在 C++ 中没有垃圾回收机制,必须自己释放分配的内存,否则会造成内存泄露。解决这个问题最有效的方法是使用智能指针 smart pointer
。智能指针是存储了指向动态分配(堆内存)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时自动地销毁动态分配的对象,防止内存泄露。智能指针的实现技术是引用计数,每使用一次内部引用计数加 1,每析构一次内部的引用计数减 1,减为 0 时删除所指向的堆内存。
C++11 中提供了三种智能指针,使用这些智能指针需要引入头文件 <memory>
:std::shared_ptr
:共享的智能指针std::unique_ptr
:独占的智能指针std::weak_ptr
:弱引用的智能指针,它不共享指针,不能操作资源,用来监视 shared_ptr。
基本用法 弱引用智能指针 std::weak_ptr
可以看做是 shared_ptr
的助手,它不管理 shared_ptr
内部的指针。std::weak_ptr
没有重载操作符 *
和 ->
,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr
中管理的资源是否存在。
初始化 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 ;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 ; }
常用方法 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 25 #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 ; }
通过打印的结果可知,虽然弱引用智能指针 wp3、wp4、wp5
监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了 weak_ptr
只是监测资源,并不管理资源。
expired() 通过调用 std::weak_ptr
类提供的 expired()
方法来判断观测的资源是否已经被释放,函数原型如下:
1 2 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 ; }
weak_ptr
监测的就是 shared_ptr
管理的资源,当共享智能指针调用 shared.reset();
之后管理的资源被释放,因此 weak.expired()
函数的结果返回 true,表示监测的资源已经不存在。
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 26 #include <iostream> #include <memory> using namespace std;int main () { shared_ptr<int > sp1, sp2; weak_ptr<int > wp; sp1 = 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 ; }
reset() 通过调用 std::weak_ptr
类提供的 reset()
方法来清空对象,使其不监测任何资源,函数原型如下:
函数的使用是非常简单的,示例代码如下:
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 ; }
weak_ptr
对象 sp 被 wp.reset();
重置之后 变成了空对象,不再监测任何资源,因此 wp.expired()
返回 true
返回管理 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 << "destruct ~Test()" << 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 ; }
执行上面的测试代码,运行中会出现异常。一个对象被析构了两次,原因:在这个例子中使用同一个指针 this
构造了两个智能指针对象 sp1 和 sp2,二者之间是没有任何关系的,因为 sp2 并不是通过 sp1 初始化得到的实例对象。在离开作用域之后 this
将被构造的两个智能指针各自析构,导致重复析构的错误。
这个问题可以通过 weak_ptr
来解决,通过 weak_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 25 26 #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 << "destruct ~Test()" << 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 ; }
强调一个细节:在调用 enable_shared_from_this
类的 shared_from_this()
方法之前,必须要先初始化函数内部 weak_ptr
对象,否则该函数无法返回一个有效的 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <memory> using namespace std;struct TA ;struct TB ;struct TA { shared_ptr<TB> bptr; ~TA () { cout << "destruct ~TA()" << endl; } };struct TB { shared_ptr<TA> aptr; ~TB () { cout << "destruct ~TB()" << 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 ; }
在测试程序中,共享智能指针 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 44 #include <iostream> #include <memory> using namespace std;struct TA ;struct TB ;struct TA { weak_ptr<TB> bptr; ~TA () { cout << "destruct ~TA()" << endl; } };struct TB { shared_ptr<TA> aptr; ~TB () { cout << "destruct ~TB()" << 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 ; }
类 TA 或者 TB 的对象被成功析构。在对类 TA 成员赋值时 ap->bptr = bp; 由于 bptr 是 weak_ptr
类型,这个赋值操作并不会增加引用计数,所以 bp 的引用计数仍然为 1,在离开作用域之后 bp 的引用计数减为 0,类 TB 的实例对象被析构。在类 TB 的实例对象被析构的时候,内部的 aptr 也被析构,其对 TA 对象的管理解除,内存的引用计数减为 1,当共享智能指针 ap 离开作用域之后,对 TA 对象的管理解除,内存的引用计数减为 0,类 TA 的实例对象被析构。
参考资料 https://subingwen.cn/cpp/weak_ptr