8.智能指针的循环引用

8.智能指针的循环引用

如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:

#include <iostream>
#include <memory>
using namespace std;

struct Test
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_ptr<Test>(this);
    }
    
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }

};

int main() 
{
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:

use_count: 1
use_count: 1
class Test is disstruct ...
class Test is disstruct ...

通过输出的结果可以看到一个对象被析构了两次,其原因是这样的:在这个例子中使用同一个指针this构造了两个智能指针对象sp1sp2,这二者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的实例对象。在离开作用域之后this将被构造的两个智能指针各自析构,导致重复析构的错误。

这个问题可以通过weak_ptr来解决,通过weak_ptr返回管理this资源的共享智能指针对象shared_ptrC++11中为我们提供了一个模板类叫做std::enable_shared_from_this,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptrlock()方法返回一个shared_ptr对象。

修改之后的代码为:

#include <iostream>
#include <memory>
using namespace std;

struct Test : public enable_shared_from_this<Test>
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_from_this();
    }
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }
};

int main() 
{
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

测试代码输出的结果为:

use_count: 1
use_count: 2
class Test is disstruct ...

最后需要强调一个细节:在调用enable_shared_from_this类的shared_from_this()方法之前,必须要先初始化函数内部weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象(具体处理方法可以参考上面的示例代码)。

shared_ptr<Test> sp2 = sp1->getSharedPtr();

等价于:

shared_ptr<Test> 临时对象 = sp1->shared_from_this();
shared_ptr<Test> sp2(临时对象);  // 拷贝构造 shared_ptr,计数+1

3.3 解决循环引用问题

智能指针如果循环引用会导致内存泄露,比如下面的例子:

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    shared_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

测试程序输出的结果如下:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 2

在测试程序中,共享智能指针apbpTATB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TATB的实例对象不能被析构,最终造成内存泄露。通过使用weak_ptr可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr,修改之后的代码如下:

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    weak_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

程序输出的结果:

TA object use_count: 1
TB object use_count: 1
TA object use_count: 2
TB object use_count: 1
class TB is disstruct ...
class TA is disstruct ...


通过输出的结果可以看到类TA或者TB的对象被成功析构了。

上面程序中,在对类TA成员赋值时ap->bptr = bp;由于bptrweak_ptr类型,这个赋值操作并不会增加引用计数,所以bp的引用计数仍然为1,在离开作用域之后bp的引用计数减为0,类TB的实例对象被析构。

在类TB的实例对象被析构的时候,内部的aptr也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。

参考资料来源:

共享智能指针

独占的智能指针

弱引用智能指针

posted @ 2023-08-02 22:16  CodeMagicianT  阅读(248)  评论(0)    收藏  举报