弱引用智能指针
文章参考:
1. 概述
弱引用智能指针std::weak_ptr
是共享智能指针std::shared_ptr
的助手,它不管理shared_ptr
内部的原始指针,也没有重载操作符*
、->
,因此不共享指针,不能操作资源,所以它的构造和析构都不会影响引用计数。其存在的意义就是监视shared_ptr
中管理的资源是否存在。
1.1 初始化
弱引用指针有构造函数如下:
-
默认构造函数:
constexpr weak_ptr() noexcept;
-
拷贝构造函数:
weak_ptr (const weak_ptr& x) noexcept; template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
-
通过
shared_ptr
对象构造:template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; int main(void){ shared_ptr<int> sp(new int(100)); weak_ptr<int> wp1; weak_ptr<int> wp2(wp1); weak_ptr<int> wp3(sp); wp1 = sp; weak_ptr<int> wp4 = wp1; return 0; }
-
分析:
- 第6行:使用默认构造函数,创建了一个空的弱引用智能指针。
- 第7行:通过已经存在的弱引用指针,创建了一个新的弱引用指针。因为原本的弱引用指针为空,所以新创建的弱引用指针也为空。
- 第8行:通过共享智能指针对象,创建了一个可用的弱引用智能指针对象,可以监管该共享智能指针对象。
- 第9行:通过共享智能指针对象,创建了一个可用的弱引用智能指针对象。(实际上是一个隐式转换)
- 第10行:通过一个弱引用智能指针对象创建一个可用的弱引用智能指针对象。(拷贝赋值函数)
1.2 常用成员方法
1.2.1 use_count()
原型:
long int use_count() const noexcept;
作用:
用于获取当前所观测资源的引用计数。
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; int main(void){ shared_ptr<int> sp(new int(100)); weak_ptr<int> wp1; weak_ptr<int> wp2(wp1); weak_ptr<int> wp3(sp); wp1 = sp; weak_ptr<int> wp4 = wp1; cout << "wp1.use_cout()==" << wp1.use_count() << endl; cout << "wp2.use_cout()==" << wp2.use_count() << endl; cout << "wp3.use_cout()==" << wp3.use_count() << endl; cout << "wp4.use_cout()==" << wp4.use_count() << endl; return 0; }
-
输出:
wp1.use_cout()==1 wp2.use_cout()==0 wp3.use_cout()==1 wp4.use_cout()==1
-
结论:虽然
wp1
、wp2
、wp3
监视的是同一块资源,但它的引用计数不会发生变化。
1.2.3 expired()
原型:
bool expired() const noexcept;
作用:
用于判断监测的资源是否已经被释放。
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; int main(void){ shared_ptr<int> sp(new int(100)); weak_ptr<int> wp(sp); cout << "wp.expired()==" << wp.expired() << endl; sp.reset(); cout << "wp.expired()==" << wp.expired() << endl; return 0; }
-
输出:
wp.expired()==0 wp.expired()==1
-
分析:弱引用指针检测的是共享智能指针
sp
管理的资源,sp
通过reset()
函数不再管理该资源,该资源的引用计数变为0,被析构了,因此wp.expired()
从1变成了0。
1.2.3 lock()
原型:
shared_ptr<element_type> lock() const noexcept;
作用:
获取所监测资源的shared_ptr
对象。
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; int main(void){ shared_ptr<int> sp1, sp2; weak_ptr<int> wp; sp1 = make_shared<int>(10); wp = sp1; sp2 = wp.lock(); cout << "wp.use_count()==" << wp.use_count() << endl; sp1.reset(); cout << "wp.use_count()==" << wp.use_count() << endl; sp1 = wp.lock(); cout << "wp.use_count()==" << wp.use_count() << endl; return 0; }
-
输出:
wp.use_count()==2 wp.use_count()==1 wp.use_count()==2
-
分析:
- 第10行:通过调用
lock()
方法,获取一个弱引用指针所监测资源的共享智能指针对象,使用该对象初始化sp2
。此时检测资源的引用计数为2
。 - 第12行:通过
reset()
函数重置共享智能指针sp1
。此时检测资源的引用计数变为1
。 - 第14行:通过调用
lock()
方法,获取一个弱引用指针所监测资源的共享智能指针对象,使用该对象初始化sp1
。此时检测资源的引用计数变回2
。
- 第10行:通过调用
1.2.4 reset()
原型:
void reset() noexcept;
作用:
重置std::weak_ptr
,使其不监测任何资源。注意:只是不检测了,但原有的资源依旧存在,还可以使用共享智能指针调用
。
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; int main(void){ shared_ptr<int> sp = make_shared<int>(10); weak_ptr<int> wp(sp); cout << "wp.expired()==" << wp.expired() << endl; cout << "wp.use_count()==" << wp.use_count() << endl; wp.reset(); cout << "wp.expired()==" << wp.expired() << endl; cout << "wp.use_count()==" << wp.use_count() << endl; cout << "*sp==" << *sp << endl; return 0; }
-
输出:
wp.expired()==0 wp.use_count()==1 wp.expired()==1 wp.use_count()==0 100
-
分析:
- 第11行:
reset()
方法让弱引用指针对象wp
不再监视任何对象,此时使用use_count()
只会返回0
,使用expired()
只会返回1
。 - 第13行:虽然弱引用指针对象
wp
被reset()
重置,不再监测任何对象了,但共享智能指针对象sp
依旧在工作。
- 第11行:
2. 返回this的shared_ptr
2.1 问题
如果在类中编写一个函数,该函数的返回值是一个管理当前对象的共享指针,那么需要注意,如果使用该函数对另一个共享智能指针进行初始化,极易导致运行时错误
。一个典型错误如下:
#include <iostream>
#include <memory>
using namespace std;
class Test {
public:
Test(){ cout << "constructor" << endl; }
~Test(){ cout << "destructor" << endl; }
shared_ptr<Test> get_shared_ptr(){
return shared_ptr<Test>(this);
}
};
int main(void){
shared_ptr<Test> sp1(new Test);
shared_ptr<Test> sp2 = sp1->get_shared_ptr();
cout << "*sp1==" << *sp1 << endl;
cout << "*sp2==" << *sp2 << endl;
return 0;
}
运行时报错如下:
constructor
*sp1==0x560e7c236eb0
*sp2==0x560e7c236eb0
destructor
destructor
free(): double free detected in tcache 2
已放弃 (核心已转储)
可以看到:命名只创建了一次对象,该内存却被析构了两次。出现这种问题的根本原因在于:sp2
实际上并不是通过sp1
构造的,二者并虽然指向一块内存,但是各自的引用计数却都是1,而不是2。因此当sp1
、sp2
声明周期结束时,会各自析构内存0x560e7c236eb0
,导致一块内存被析构了两次。
2.2 方法
2.1中的问题可以通过weak_ptr
解决。C++11为我们提供了一个模板类std::enabled_shared_from_this<T>
,这个类中有一个方法叫做shared_from_this()
,通过该方法可以返回一个共享智能指针。其内部实现逻辑为:使用weak_ptr
来监测this
对象,并通过调用weak_ptr
的lock()
方法返回一个shared_ptr
对象。
EG:
-
代码:
#include <iostream> #include <memory> using namespace std; class Test: public enable_shared_from_this<Test> { public: Test(){ cout << "constructor" << endl; } ~Test(){ cout << "destructor" << endl; } shared_ptr<Test> get_shared_ptr(){ return shared_from_this(); } }; int main(void){ shared_ptr<Test> sp1(new Test); shared_ptr<Test> sp2 = sp1->get_shared_ptr(); cout << "*sp1==" << *sp1 << endl; cout << "*sp2==" << *sp2 << endl; return 0; }
-
输出:
constructor *sp1==0x560a2c3cbeb0 *sp2==0x560a2c3cbeb0 destructor
注意:在调用enable_shared_from_this
类的shared_from_this()
方法之前,必须要先初始化函数内部的weak_ptr
对象,否则该函数无法返回一个有效的shared_ptr
对象。这是因为shared_from_this()
方法的内在逻辑就是通过弱引用指针监测this
,然后调用lock()
方法返回共享智能指针对象。
3. 循环引用问题
3.1 错误
智能指针如果存在循环引用,会导致内存泄漏
。案例如下:
-
代码:
#include <iostream> #include <memory> using namespace std; class A; class B; class A { public: shared_ptr<B> sp; A() { cout << "A constructor" << endl; } ~A() { cout << "A destructor" << endl; } }; class B { public: shared_ptr<A> sp; B() { cout << "B constructor" << endl; } ~B() { cout << "B destructor" << endl; } }; int main(void){ shared_ptr<A> sp_a(new A()); shared_ptr<B> sp_b(new B()); cout << "sp_a.use_count()==" << sp_a.use_count() << endl; cout << "sp_b.use_count()==" << sp_b.use_count() << endl; // 循环引用 sp_a->sp = sp_b; sp_b->sp = sp_a; cout << "sp_a.use_count()==" << sp_a.use_count() << endl; cout << "sp_b.use_count()==" << sp_b.use_count() << endl; return 0; }
-
输出:
A constructor B constructor sp_a.use_count()==1 sp_b.use_count()==1 sp_a.use_count()==2 sp_b.use_count()==2
-
分析:可以看到,共享智能指针指向的内存最后并没有被析构。这是因为程序中的共享智能指针形成了循环引用的问题,
sp_a
指向sp_a->sp
,sp_a->sp
指向sp_b
,sp_b
指向sp_b->sp
,sp_b->sp
指向sp_a
,循环引用形成,导致sp_a
和sp_b
的引用计数发生错误,当共享智能指针离开作用域时引用计数只能减到1,因此无法释放目标内存。
3.2 方法
通过将A
类或B
类的智能引用成员修改为弱引用智能指针即,打破循环引用链条即可。
-
代码:
#include <iostream> #include <memory> using namespace std; class A; class B; class A { public: weak_ptr<B> sp; // 修改为弱引用指针 A() { cout << "A constructor" << endl; } ~A() { cout << "A destructor" << endl; } }; class B { public: shared_ptr<A> sp; B() { cout << "B constructor" << endl; } ~B() { cout << "B destructor" << endl; } }; int main(void){ shared_ptr<A> sp_a(new A()); shared_ptr<B> sp_b(new B()); cout << "sp_a.use_count()==" << sp_a.use_count() << endl; cout << "sp_b.use_count()==" << sp_b.use_count() << endl; // 循环引用 sp_a->sp = sp_b; sp_b->sp = sp_a; cout << "sp_a.use_count()==" << sp_a.use_count() << endl; cout << "sp_b.use_count()==" << sp_b.use_count() << endl; return 0; }
-
输出:
A constructor B constructor sp_a.use_count()==1 sp_b.use_count()==1 sp_a.use_count()==2 sp_b.use_count()==1 B destructor A destructor