配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:
在 C++ 中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
C++11 中提供了三种智能指针,使用这些智能指针时需要引用头文件 :
std::shared_ptr:共享的智能指针
std::unique_ptr:独占的智能指针
std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视 shared_ptr 的。
- 基本使用方法 {#1-基本使用方法} =====================
弱引用智能指针std::weak_ptr
可以看做是shared_ptr
的助手,它不管理shared_ptr
内部的指针。std::weak_ptr
没有重载操作符*
和->
,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr
中管理的资源是否存在。
1.1 初始化 {#1-1-初始化}
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| // 默认构造函数 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;
|
在C++11中,weak_ptr
的初始化可以通过以上提供的构造函数来完成初始化,具体使用方法如下:
|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> sp(new int); weak_ptr<int> wp1; weak_ptr<int> wp2(wp1); weak_ptr<int> wp3(sp); weak_ptr<int> wp4; wp4 = sp; weak_ptr<int> wp5; wp5 = wp3; return 0; }
|
weak_ptr<int> wp1;
构造了一个空weak_ptr
对象weak_ptr<int> wp2(wp1);
通过一个空weak_ptr
对象构造了另一个空weak_ptr
对象weak_ptr<int> wp3(sp);
通过一个shared_ptr
对象构造了一个可用的weak_ptr
实例对象wp4 = sp;
通过一个shared_ptr
对象构造了一个可用的weak_ptr
实例对象(这是一个隐式类型转换)wp5 = wp3;
通过一个weak_ptr
对象构造了一个可用的weak_ptr
实例对象
1.2 其他常用方法 {#1-2-其他常用方法}
1.2.1 use_count() {#1-2-1-use-count}
通过调用std::weak_ptr
类提供的use_count()
方法可以获得当前所观测资源的引用计数,函数原型如下:
|-------------|-----------------------------------------------------------------|
| 1 2
| // 函数返回所监测的资源的引用计数 long int use_count() const noexcept;
|
修改一下上面的测试程序,添加打印资源引用计数的代码:
|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> sp(new int); weak_ptr<int> wp1; weak_ptr<int> wp2(wp1); weak_ptr<int> wp3(sp); weak_ptr<int> wp4; wp4 = sp; weak_ptr<int> wp5; wp5 = wp3; cout << "use_count: " << endl; cout << "wp1: " << wp1.use_count() << endl; cout << "wp2: " << wp2.use_count() << endl; cout << "wp3: " << wp3.use_count() << endl; cout << "wp4: " << wp4.use_count() << endl; cout << "wp5: " << wp5.use_count() << endl; return 0; }
|
测试程序输出的结果为:
|---------------------|-------------------------------------------------------|
| 1 2 3 4 5 6
| use_count: wp1: 0 wp2: 0 wp3: 1 wp4: 1 wp5: 1
|
通过打印的结果可以知道,虽然弱引用智能指针wp3
、wp4
、wp5
监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了weak_ptr只是监测资源,并不管理资源
。
1.2.3 expired() {#1-2-3-expired}
通过调用std::weak_ptr
类提供的expired()
方法来判断观测的资源是否已经被释放,函数原型如下:
|-------------|-----------------------------------------------------------------------------|
| 1 2
| // 返回true表示资源已经被释放, 返回false表示资源没有被释放 bool expired() const noexcept;
|
函数的使用方法如下:
|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> shared(new int(10)); weak_ptr<int> weak(shared); cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl; shared.reset(); cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl; return 0; }
|
测试代码输出的结果:
|-------------|---------------------------------------------------|
| 1 2
| 1. weak is not expired 2. weak is expired
|
weak_ptr
监测的就是shared_ptr
管理的资源,当共享智能指针调用shared.reset();
之后管理的资源被释放,因此weak.expired()
函数的结果返回true
,表示监测的资源已经不存在了。
1.2.3 lock() {#1-2-3-lock}
通过调用std::weak_ptr
类提供的lock()
方法来获取管理所监测资源的shared_ptr
对象,函数原型如下:
|-----------|---------------------------------------------------------|
| 1
| shared_ptr<element_type> lock() const noexcept;
|
函数的使用方法如下:
|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> sp1, sp2; weak_ptr<int> wp; sp1 = std::make_shared<int>(520); wp = sp1; sp2 = wp.lock(); cout << "use_count: " << wp.use_count() << endl; sp1.reset(); cout << "use_count: " << wp.use_count() << endl; sp1 = wp.lock(); cout << "use_count: " << wp.use_count() << endl; cout << "*sp1: " << *sp1 << endl; cout << "*sp2: " << *sp2 << endl; return 0; }
|
测试代码输出的结果为:
|-------------------|--------------------------------------------------------------------|
| 1 2 3 4 5
| use_count: 2 use_count: 1 use_count: 2 *sp1: 520 *sp2: 520
|
sp2 = wp.lock();
通过调用lock()
方法得到一个用于管理weak_ptr
对象所监测的资源的共享智能指针对象,使用这个对象初始化sp2
,此时所监测资源的引用计数为2
sp1.reset();
共享智能指针sp1被重置,weak_ptr
对象所监测的资源的引用计数减1sp1 = wp.lock();
sp1重新被初始化,并且管理的还是weak_ptr
对象所监测的资源,因此引用计数加1- 共享智能指针对象
sp1
和sp2
管理的是同一块内存,因此最终打印的内存中的结果是相同的,都是520
1.2.4 reset() {#1-2-4-reset}
通过调用std::weak_ptr
类提供的reset()
方法来清空对象,使其不监测任何资源,函数原型如下:
|-----------|--------------------------------|
| 1
| void reset() noexcept;
|
函数的使用是非常简单的,示例代码如下:
|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl; wp.reset(); cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl; return 0; }
|
测试代码输出的结果为:
|-------------|-----------------------------------------------|
| 1 2
| 1. wp is not expired 2. wp is expired
|
weak_ptr
对象sp
被重置之后wp.reset();
变成了空对象,不再监测任何资源,因此wp.expired()
返回true
- 返回管理this的shared_ptr {#2-返回管理this的shared-ptr}
如果在一个类中编写了一个函数,通过这个得到管理当前对象的共享智能指针,我们可能会写出如下代码:
|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #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; }
|
执行上面的测试代码,运行中会出现异常,在终端还是能看到对应的日志输出:
|-----------------|-------------------------------------------------------------------------------------------|
| 1 2 3 4
| use_count: 1 use_count: 1 class Test is disstruct ... class Test is disstruct ...
|
通过输出的结果可以看到一个对象被析构了两次
,其原因是这样的:在这个例子中使用同一个指针this
构造了两个智能指针对象sp1
和sp2
,这二者之间是没有任何关系的,因为sp2
并不是通过sp1
初始化得到的实例对象。在离开作用域之后this
将被构造的两个智能指针各自析构,导致重复析构的错误。
这个问题可以通过weak_ptr
来解决,通过wek_ptr
返回管理this
资源的共享智能指针对象shared_ptr
。C++11中为我们提供了一个模板类叫做std::enable_shared_from_this<T>
,这个类中有一个方法叫做shared_from_this()
,通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr
来监测this
对象,并通过调用weak_ptr
的lock()
方法返回一个shared_ptr
对象。
修改之后的代码为:
|------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #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; }
|
测试代码输出的结果为:
|---------------|---------------------------------------------------------------|
| 1 2 3
| use_count: 1 use_count: 2 class Test is disstruct ...
|
最后需要强调一个细节:在调用enable_shared_from_this 类的shared_from_this()方法之前,必须要先初始化函数内部weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象(具体处理方法可以参考上面的示例代码)。
- 解决循环引用问题 {#3-解决循环引用问题}
智能指针如果循环引用会导致内存泄露,比如下面的例子:
|---------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #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; }
|
测试程序输出的结果如下:
|-----------------|-----------------------------------------------------------------------------------------------------|
| 1 2 3 4
| TA object use_count: 1 TB object use_count: 1 TA object use_count: 2 TB object use_count: 2
|
在测试程序中,共享智能指针ap
、bp
对TA
、TB
实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1
,这种情况下不会去删除智能指针管理的内存,导致类TA
、TB
的实例对象不能被析构,最终造成内存泄露。通过使用weak_ptr
可以解决这个问题,只要将类TA
或者TB
的任意一个成员改为weak_ptr
,修改之后的代码如下:
|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #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; }
|
程序输出的结果:
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| 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;
由于bptr
是weak_ptr
类型,这个赋值操作并不会增加引用计数,所以bp
的引用计数仍然为1,在离开作用域之后bp
的引用计数减为0,类TB
的实例对象被析构。
在类TB
的实例对象被析构的时候,内部的aptr
也被析构,其对TA
对象的管理解除,内存的引用计数减为1,当共享智能指针ap
离开作用域之后,对TA
对象的管理也解除了,内存的引用计数减为0,类TA
的实例对象被析构。