shared_ptr循环引用问题
循环引用指的是两个或多个对象之间相互持有对方的引用,形成一个闭环的引用关系。换句话说,每个对象都拥有对其他对象的引用,导致这些对象的引用计数无法归零,从而无法释放它们所占用的内存。
循环引用通常发生在使用智能指针(如shared_ptr
)进行内存管理的情况下。当两个或多个对象使用shared_ptr
相互持有对方的指针时,每个对象的引用计数都至少为1。即使在没有外部引用的情况下,这些对象的引用计数也不会降为0,因为它们彼此之间保持着引用。
由于引用计数无法降为0,这些对象的析构函数永远不会被调用,导致资源泄漏。这是因为智能指针在引用计数归零时才会自动释放所管理的内存。循环引用阻止了引用计数归零,因此会造成内存泄漏的问题。
代码示例
#include <iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
A()
{
cout<<"A() B count:"<<m_spB.use_count()<<endl;
}
~A()
{
cout<<"~A() B count:"<<m_spB.use_count()<<endl;
}
shared_ptr<B> m_spB;
};
class B
{
public:
B()
{
cout<<"B() A count:"<<m_spA.use_count()<<endl;
}
~B()
{
cout<<"~B() A count:"<<m_spA.use_count()<<endl;
}
shared_ptr<A> m_spA;
};
int main()
{
shared_ptr<A>spA(new A());
shared_ptr<B>spB(new B());
spB->m_spA = spA;
spA->m_spB = spB;
return 0;
}
上述代码指针指向示意图图
解决办法
为了解决循环引用的问题,可以使用weak_ptr来取代shared_ptr。weak_ptr允许对象之间存在引用,但不会增加引用计数,也不会阻止对象的销毁。通过将m_spA和m_spB改为weak_ptr类型,可以避免循环引用导致的内存泄漏。
修改代码如下
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
A()
{
cout << "A() B count: " << m_spB.use_count() << endl;
}
~A()
{
cout << "~A() B count: " << m_spB.use_count() << endl;
}
weak_ptr<B> m_spB;
};
class B
{
public:
B()
{
cout << "B() A count: " << m_spA.use_count() << endl;
}
~B()
{
cout << "~B() A count: " << m_spA.use_count() << endl;
}
weak_ptr<A> m_spA;
};
int main()
{
shared_ptr<A> spA(new A());
shared_ptr<B> spB(new B());
spB->m_spA = spA;
spA->m_spB = spB;
return 0;
}
weak_ptr介绍
weak_ptr
是C++标准库中智能指针的一种类型,用于解决循环引用问题和避免悬挂指针(dangling pointer)的情况发生。与shared_ptr
不同,weak_ptr
并不拥有所指向对象的所有权,它只是对被管理对象的一个观察者。
主要作用如下:
- 解决循环引用问题:当两个或多个对象相互持有对方的
shared_ptr
时,形成循环引用。这种情况下,对象的引用计数永远不会变为0,导致内存泄漏。通过使用weak_ptr
可以打破循环引用,避免内存泄漏的发生。
代码见上面示例;
- 观察资源的生命周期:
weak_ptr
允许你观察由shared_ptr
管理的资源的生命周期,而不会延长资源的生命周期。你可以通过expired()
函数检查所观察的对象是否已经被释放。
代码举例
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "MyClass Constructor" << std::endl;
}
~MyClass() {
std::cout << "MyClass Destructor" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> sharedPtr(new MyClass());
std::weak_ptr<MyClass> weakPtr(sharedPtr);
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Object is alive" << std::endl;
} else {
std::cout << "Object has been destroyed" << std::endl;
}
// 销毁 sharedPtr,对象被释放
sharedPtr.reset();
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Object is alive" << std::endl;
} else {
std::cout << "Object has been destroyed" << std::endl;
}
return 0;
}
在这个例子中,我们创建了一个名为MyClass的类,并使用shared_ptr进行内存管理。然后,将该shared_ptr转换为weak_ptr。
首先,我们通过调用lock()函数创建了一个新的shared_ptr,命名为lockedPtr。在这种情况下,由于对象存在,lock()函数返回一个有效的shared_ptr,因此输出Object is alive。
接下来,我们通过调用sharedPtr.reset()销毁了shared_ptr,从而释放了所管理的对象。此时,weak_ptr指向的对象已经被销毁。
再次调用lock()函数创建新的shared_ptr,此时由于对象已经被销毁,lock()函数将返回一个空的shared_ptr。因此,输出Object has been destroyed。
通过使用lock()函数和条件语句,我们可以观察由shared_ptr管理的资源是否仍然存在,从而确定对象的生命周期状态。如果lock()函数返回一个有效的shared_ptr,表示对象仍然存活;如果返回一个空的shared_ptr,表示对象已经被销毁。这样可以安全地观察对象的生命周期并采取相应的操作。
- 安全地访问对象:通过调用
lock()
函数,可以从weak_ptr
获得一个有效的shared_ptr
来安全地访问所指向的对象。如果对象仍然存在,则lock()
函数返回一个有效的shared_ptr
;如果对象已经被销毁,则返回一个空的shared_ptr
。
代码举例
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "MyClass Constructor" << std::endl;
}
~MyClass() {
std::cout << "MyClass Destructor" << std::endl;
}
void SomeFunction() {
std::cout << "SomeFunction called" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> sharedPtr(new MyClass());
std::weak_ptr<MyClass> weakPtr(sharedPtr);
sharedPtr->SomeFunction(); // 正常调用
// 销毁 sharedPtr,对象被释放
sharedPtr.reset();
std::shared_ptr<MyClass> newSharedPtr = weakPtr.lock();
if (newSharedPtr) {
newSharedPtr->SomeFunction(); // 安全调用,对象存在
} else {
std::cout << "Object has been destroyed" << std::endl; // 对象已经被销毁
}
return 0;
}
- 防止悬挂指针(dangling pointer):当你使用
shared_ptr
来管理对象,并且在某处需要引用该对象但不拥有所有权时,可以使用weak_ptr
。这样可以避免悬挂指针的情况发生,即在对象已经被销毁后仍然持有指向它的指针。
代码见上面示例;
总之,weak_ptr
提供了一种非拥有性的、观察所管理对象生命周期的方式,能够解决循环引用和悬挂指针等问题,增加了智能指针的灵活性和安全性。