智能指针share_ptr使用过程中需要注意的几点

智能指针在boost中很早就有了,在tr1上也很早,但是没怎么用,后来0x标准出来之后,智能指针变成了标准库,所以现在用起来就不区分boost和std了。

主要说下share_ptr的几个注意点,待补全。

1.环状的链式结构可能会形成内存泄露
例如:

class BaseClass;
class ChildClass;

typedef std::shared_ptr BaseClassPtr;
typedef std::shared_ptr ChildClassPtr;

class BaseClass
{
public:
    ChildClassPtr childClass;
protected:
private:
};

class ChildClass
{
public:
    BaseClassPtr baseClass;
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    BaseClassPtr base(new BaseClass());
    ChildClassPtr child(new ChildClass());

    base->childClass = child;
    child->baseClass = base;


    system("pause");
    return 0;
}

2.多线程环境下使用代价更大。

因为share_ptr内部有两个数据成员,一个是指向对象的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象,读写操作不能原子化,(具体的结构图可以查看 陈硕的文章《为什么多线程读写 shared_ptr 要加锁?》)所以多线程下要么加锁,要么小心翼翼使用share_ptr。

例如:

class Test
{
public:
    Test() {}

    ~Test() {}

    // ...
protected:
private:
};

void func(std::shared_ptr test_ptr)
{
    // 大量使用test_ptr

    std::shared_ptr temp_ptr = test_ptr;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr sp(new Test());
    boost::thread th1(std::bind(&func, sp));
    boost::thread th2(std::bind(&func, sp));

    th1.join();
    th2.join();

    return 0;
}

上面的代码不知道什么时候可能就宕了,而且不容易找到问题,这个时候你就得硬看代码了。

你也可以通过使用weak_ptr来解决这个问题,例如上述例子可以修改为:

class Test
{
public:
    Test() {}

    ~Test() {}

    // ...
protected:
private:
};

void func(std::weak_ptr test_ptr)
{
    // 大量使用test_ptr

    std::weak_ptr temp_ptr = test_ptr;
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr sp(new Test());
    std::weak_ptr wp(sp);

    boost::thread th1(std::bind(&func, wp));
    boost::thread th2(std::bind(&func, wp));

    th1.join();
    th2.join();

    return 0;
}

weak_ptr是一种可构造可赋值的不增加引用计数来管理share_ptr的智能指针,它可以非常方便的通过weak_ptr.lock()转为share_ptr,通过weak_ptr.expired()来判断智能指针是否被释放,还是非常方便的。条目1中的例子使用weak_ptr就可以解决问题

3.share_ptr包装this的时候使用enable_shared_from_this

class Test
{
public:
    Test() {}

    ~Test() {}

    std::shared_ptr get_ptr()
    {
        return std::shared_ptr(this);
    }

    // ...
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    Test t;
    std::shared_ptr t_ptr(t.get_ptr());
    return 0;
}

这样就会发生析构两次的问题,可以使用enable_shared_from_this来做共享,上面例子修改为

class Test : public std::enable_shared_from_this
{
public:
    Test() {}

    ~Test() {}

    std::shared_ptr get_ptr()
    {
        return shared_from_this();
    }

    // ...
protected:
private:
};

int _tmain(int argc, _TCHAR* argv[])
{
    std::shared_ptr t_ptr(new Test());
    t_ptr->get_ptr();

    return 0;
}

4.share_ptr多次引用同一数据会导致内存多次释放

int _tmain(int argc, _TCHAR* argv[])
{
    int* int_ptr = new int[100];
    std::shared_ptr s_int_ptr1(int_ptr);

    // do something

    std::shared_ptr s_int_ptr2(int_ptr);

    return 0;
}

而且C++之父对share_ptr的初衷是:“shared_ptr用于表示共享拥有权。然而共享拥有权并不是我的初衷。在我看来,一个更好的办法是为对象指明拥有者并且为对象定义一个可以预测的生存范围。”

 

对倒数第二种情况在一步的分析:

类向外传递this与shared_ptr

可以说,shared_ptr着力解决类对象一级的资源管理,至于类对象内部,shared_ptr暂时还无法管理;那么这是否会出现问题呢?来看看这样的代码:

  1. class Point1  
  2. {  
  3. public:  
  4.     Point1() :  X(0), Y(0) { cout << "Point1::Point1(), (" << X << "," << Y << ")" << endl; }  
  5.     Point1(int x, int y) :  X(x), Y(y) { cout << "Point1::Point1(int x, int y), (" << X << "," << Y << ")" << endl; }  
  6.     ~Point1() { cout << "Point1::~Point1(), (" << X << "," << Y << ")" << endl; }  
  7.        
  8. public:  
  9.     Point1* Add(const Point1* rhs) { X += rhs->X; Y += rhs->Y; return this;}  
  10.    
  11. private:  
  12.     int X;  
  13.     int Y;  
  14. };  
  15.    
  16. void TestPoint1Add()  
  17. {  
  18.     cout << "TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;  
  19.     shared_ptr p1( new Point1(2,2) );  
  20.     shared_ptr p2( new Point1(3,3) );  
  21.        
  22.     p2.reset( p1->Add(p2.get()) );  
  23. }  
  24.    
  25. 输出为:  
  26. TestPoint1Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  27. Point1::Point1(int x, int y), (2,2)  
  28. Point1::Point1(int x, int y), (3,3)  
  29. Point1::~Point1(), (3,3)  
  30. Point1::~Point1(), (5,5)  
  31. Point1::~Point1(), (5411568,5243076)  

为了使类似Point::Add()::Add()可以连续进行Add操作成为可能,Point1定义了Add方法,并返回了this指针(从Effective C++的条款看,这里最好该以传值形式返回临时变量,在此为了说明问题,暂且不考虑这种设计是否合理,但他就这样存在了)。在TestPoint1Add()函数中,使用此返回的指针重置了p2,这样p2和p1就同时管理了同一个对象,但是他们却互相不知道这事儿,于是悲剧发生了。在作用域结束的时候,他们两个都去对所管理的资源进行析构,从而出现了上述的输出。从最后一行输出也可以看出,所管理的资源,已经处于“无效”的状态了。

 

那么,我们是否可以改变一下呢,让Add返回一个shared_ptr了呢。我们来看看Point2:

  1. class Point2  
  2. {  
  3. public:  
  4.     Point2() :  X(0), Y(0) { cout << "Point2::Point2(), (" << X << "," << Y << ")" << endl; }  
  5.     Point2(int x, int y) :  X(x), Y(y) { cout << "Point2::Point2(int x, int y), (" << X << "," << Y << ")" << endl; }  
  6.     ~Point2() { cout << "Point2::~Point2(), (" << X << "," << Y << ")" << endl; }  
  7.        
  8. public:  
  9.     shared_ptr Add(const Point2* rhs) { X += rhs->X; Y += rhs->Y; return shared_ptr(this);}  
  10.    
  11. private:  
  12.     int X;  
  13.     int Y;  
  14. };  
  15.    
  16. void TestPoint2Add()  
  17. {  
  18.     cout << endl << "TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;  
  19.     shared_ptr p1( new Point2(2,2) );  
  20.     shared_ptr p2( new Point2(3,3) );  
  21.        
  22.     p2.swap( p1->Add(p2.get()) );  
  23. }  
  24.    
  25. 输出为:  
  26. TestPoint2Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  27. Point2::Point2(int x, int y), (2,2)  
  28. Point2::Point2(int x, int y), (3,3)  
  29. Point2::~Point2(), (3,3)  
  30. Point2::~Point2(), (5,5)  
  31. Point2::~Point2(), (3379952,3211460)  

从输出来看,哪怕使用shared_ptr来作为Add函数的返回值,仍然无济于事;对象仍然被删除了两次;

 针对这种情况,shared_ptr的解决方案是: enable_shared_from_this这个模版类。所有需要在内部传递this指针的类,都从enable_shared_from_this继承;在需要传递this的时候,使用其成员函数shared_from_this()来返回一个shared_ptr。运用这种方案,我们改良我们的Point类,得到如下的Point3:

  1. class Point3 : public enable_shared_from_this  
  2. {  
  3. public:  
  4.     Point3() :  X(0), Y(0) { cout << "Point3::Point3(), (" << X << "," << Y << ")" << endl; }  
  5.     Point3(int x, int y) :  X(x), Y(y) { cout << "Point3::Point3(int x, int y), (" << X << "," << Y << ")" << endl; }  
  6.     ~Point3() { cout << "Point3::~Point3(), (" << X << "," << Y << ")" << endl; }  
  7.        
  8. public:  
  9.     shared_ptr Add(const Point3* rhs) { X += rhs->X; Y += rhs->Y; return shared_from_this();}  
  10.    
  11. private:  
  12.     int X;  
  13.     int Y;  
  14. };  
  15.    
  16. void TestPoint3Add()  
  17. {  
  18.     cout << endl << "TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;  
  19.     shared_ptr p1( new Point3(2,2) );  
  20.     shared_ptr p2( new Point3(3,3) );  
  21.        
  22.     p2.swap( p1->Add(p2.get()) );  
  23. }  
  24. 输出为:  
  25. TestPoint3Add() >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  
  26. Point3::Point3(int x, int y), (2,2)  
  27. Point3::Point3(int x, int y), (3,3)  
  28. Point3::~Point3(), (3,3)  
  29. Point3::~Point3(), (5,5)  

从这个输出可以看出,在这里的对象析构已经变得正常。因此,在类内部需要传递this的场景下,enable_shared_from_this是一个比较靠谱的方案;只不过,要谨慎的记住,使用该方案的一个前提,就是类的对象已经被shared_ptr管理,否则,就等着抛异常吧。例如:

  1. Point3 p1(10, 10);  
  2. Point3 p2(20, 20);  
  3.    
  4. p1.Add( &p2 ); //此处抛异常  
上面的代码会导致crash。那是因为p1没有被shared_ptr管理。之所以这样,是由于shared_ptr的构造函数才会去初始化enable_shared_from_this相关的引用计数(具体可以参考代码),所以如果对象没有被shared_ptr管理,shared_from_this()函数就会出错。

 于是,shared_ptr又引入了注意事项:

  • 若要在内部传递this,请考虑从enable_shared_from_this继承
  • 若从enable_shared_from_this继承,则类对象必须让shared_ptr接管。
  • 如果要使用智能指针,那么就要保持一致,统统使用智能智能,尽量减少raw pointer裸指针的使用。

 好嘛,到最后,再做一个总结:

  • C++没有垃圾收集,资源管理需要自己来做。
  • 智能指针可以部分解决资源管理的工作,但是不是万能的。
  • 使用智能指针的时候,每个shared_ptr对象都应该有一个名字;也就是避免在一个表达式内做多个资源的初始化;
  • 避免shared_ptr的交叉引用;使用weak_ptr打破交叉;
  • 使用enable_shared_from_this机制来把this从类内部传递出来;
  • 资源管理保持统一风格,要么使用智能指针,要么就全部自己管理裸指针;


上述情况出现的场合:

使用boost库时,经常会看到如下的类

class A:public enable_share_from_this

在什么情况下要使类A继承enable_share_from_this?

使用场合:当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。

我们就使类A继承enable_share_from_this,然后通过其成员函数share_from_this()返回当指向自身的share_ptr。

以上有2个疑惑:

1.把当前类对象作为参数传给其他函数时,为什么要传递share_ptr呢?直接传递this指针不可以吗?

一个裸指针传递给调用者,谁也不知道调用者会干什么?假如调用者delete了该对象,而share_tr此时还指向该对象。

2.这样传递share_ptr可以吗?share_ptr

这样会造成2个非共享的share_ptr指向一个对象,最后造成2次析构该对象。

实现原理:

share_from_this()是如何返回指向该对象的share_ptr的?显然它不能直接return share_ptr。因为它返回的share_ptr必须和管理该对象的share_ptr是共享的,也就是说返回的share_ptr的引用计数和管理该对象的share_ptr的引用计数是相同的。其实enable_share_from_this就存储了管理该对象的share_ptr的引用计数,通过weak_ptr来实现。在enable_share_from_this,里有一个成员weak_this_。


你可能感兴趣的:(C++)