shared_ptr 的循环引用问题
在 C++ 开发过程中,面对浩繁的代码工作,不免会出现各种 bug,而在智能指针使用方面也存在一些难以察觉的坑。shared_ptr 循环引用造成内存泄漏就是一例。当两个内存对象分别拥有对方的非 nullptr 的 shared_ptr 类型的智能指针,导致 shared_ptr 引用计数无法归 0,从而造成系统内存泄漏。
我们来看下面的代码
class B;
class A
{
public:
A() = default;
~A() { std::cout << "A dtor." << std::endl;}
std::shared_ptr<B> b;
};
class B
{
public:
B() = default;
~B() { std::cout << "B dtor." << std::endl; }
std::shared_ptr<A> a;
};
上面就是典型的错误场景,在实际运行过程中,我们无法看到 A,B 的析构。
int main()
{
std::shared_ptr<A> sa = std::make_shared<A>();
std::shared_ptr<B> sb = std::make_shared<B>();
sa->b = sb;
sb->a = sa;
return 0;
}
对于 shared_ptr 循环引用的问题,怎么来解决呢?
- 在实际代码编写过程中,尽量使用 unique_ptr,避免出现循环引用的场景
unique_ptr 只能独占使用,我们可以通过右值或裸指针将 unique_ptr 的内存对象传递给其它内存对象。
class B; class A { public: std::unique_ptr<B> b; }; class B { public: void test() {} }; int main() { std::unique_ptr<A> a = std::make_unique<A>(); std::unique_ptr<B> b = std::make_unique<B>(); a->b = std::move(b); a->b->test(); // b->get() is nullptr }
- 如果无法避免循环引用,则改用 weak_ptr
weak_ptr 并不会增加内存对象的引用计数,为避免内存泄漏,循环引用使用 weak_ptr
我们无法通过 weak_ptr 直接调用内存对象的成员和方法,可以通过 weak_ptr 的 lock() 方法获得它的 shared_ptr,然后再调用内存对象的成员和方法。通过 weak_ptr 的 expired() 方法可以判断它所指向的内存对象是否已被释放。class B; class A { public: A() = default; ~A() { std::cout << "A dtor." << std::endl; } void test() { std::cout << "A test." << std::endl; } std::weak_ptr<B> b; }; class B { public: B() = default; ~B() { std::cout << "B dtor." << std::endl; } void test() { std::cout << "B test." << std::endl; } std::weak_ptr<A> a; };
上述代码运行后,A,B 都会被正常析构。int main() { std::shared_ptr<WeakPtr::A> sa = std::make_shared<WeakPtr::A>(); std::shared_ptr<WeakPtr::B> sb = std::make_shared<WeakPtr::B>(); sa->b = sb; sb->a = sa; // shared_ptr call test sa->test(); sb->test(); // weak_ptr call test sa->b.lock()->test(); sb->a.lock()->test(); // released std::cout << "B expired = " << sa->b.expired() << std::endl; std::cout << "A expired = " << sb->a.expired() << std::endl; }