当涉及到 std::shared_ptr
的循环引用时,引用计数不会归零的原因是因为每个对象都被另一个对象以 shared_ptr
形式持有,使得它们的生命周期被相互延续。下面我将通过一个具体的例子来详细解释这个现象:
示例解析
假设有两个类 A
和 B
,它们互相持有对方的 shared_ptr
:
class A {
public:
std::shared_ptr<B> b_ptr; // A 持有一个指向 B 的 shared_ptr
~A() {
std::cout << "A destructor called\n";
}
};
class B {
public:
std::shared_ptr<A> a_ptr; // B 持有一个指向 A 的 shared_ptr
~B() {
std::cout << "B destructor called\n";
}
};
在 main
函数中创建并相互关联:
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
}
分析引用计数
-
创建
a
和b
:- 创建
a
时,A
对象的实例的引用计数变为 1。 - 创建
b
时,B
对象的实例的引用计数变为 1。
- 创建
-
相互关联:
- 当
a->b_ptr = b;
执行时,b
的引用计数增加到 2。 - 当
b->a_ptr = a;
执行时,a
的引用计数增加到 2。
- 当
-
函数结束时的状态:
- 由于
a
和b
都是通过shared_ptr
相互持有对方,它们的引用计数都是 2,没有降到 0。 main
函数结束时,a
和b
的局部shared_ptr
对象被销毁,每个对象的引用计数从 2 减到 1。- 但因为它们相互持有对方,这个引用计数停留在 1 而不是 0。
- 由于
引用计数不归零的后果
由于引用计数没有归零,A
和 B
的析构函数都不会被调用,导致内存泄漏。这是因为 std::shared_ptr
认为还有有效的 shared_ptr
实例指向这些对象,因此它不会释放这些对象占用的内存。
如何避免这种情况
避免循环引用的一个常用方法是将其中一个类的 shared_ptr
替换为 std::weak_ptr
。weak_ptr
允许引用一个对象,但不增加其引用计数,因此不会阻碍对象的销毁:
class B {
public:
std::weak_ptr<A> a_ptr; // 使用 weak_ptr 而不是 shared_ptr
~B() {
std::cout << "B destructor called\n";
}
};
在这种情况下,即使 A
类仍然使用 shared_ptr
持有 B
,B
使用 weak_ptr
持有 A
不会增加 A
的引1兰数,从而允许 A
的引用计数在没有其他 shared_ptr
指向它时归零,确保资源得到释放。这样一来,循环引用被有效地打破,对象可以正常销毁,避免了内存泄漏。