shared_ptr和weak_ptr以及循环引用问题
shared_ptr的出现:
使用裸指针对堆对象(动态内存)进行管理是极其容易出现问题的。例如:忘记释放内存造成的内存泄漏、尚有指针引用内存的情况下释放了该内存等等的问题。
为此能够更加智能地保留或者释放堆(动态)对象,标准库以及boost库提供了智能指针。智能指针负责自动释放所指向的对象。智能指针的使用和普通指针类似,解引用一个智能指针返回它指的对象。
shared_ptr:允许多个指针指向同一个对象。
shared_ptr用法:
#include <iostream> #include <memory> using namespace std; class Test { public: Test(string s):_str(s) { cout << "Test create" << endl; } ~Test() { cout << "Test delete" << endl; } string &getStr() { return _str; } void setStr(string s) { _str = s; } void print() { cout << _str << endl; } private: string _str; };
int main() { shared_ptr<Test> p1 = make_shared<Test>("pTest1"); shared_ptr<Test> p2 = make_shared<Test>("pTest2"); shared_ptr<Test> p3 = make_shared<Test>("pTest3"); p3 = p1; p2 = p1; cout << p3.use_count() << endl; cout << p1.use_count() << endl; return 0; }
将p1智能指针赋值给了p3背后发生的事情:p3所指向的对象引用计数-1后为0,释放p3所指向的对象。p1的引用计数+1。
问题:通过p3可以拿到所指向的引用计数值为3,通过p1也能拿到所指向的引用计数为3。是否p1智能指针对象和p3智能指针对象都保存了一份引用计数值呢?
参考《c++ primer第五版》P402页所给出的解释:
到底是用一个计数器还是其他数据结构来记录有多少指针共享对象, 完全由标准库的具体实现来决定。关键是智能指针类能记录有多少个shared_ptr指向相同的对象,并能在恰当的时候自动释放对象。
虽然智能指针能自动释放内存,但是使用不当同样会导致内存泄漏。
shared_ptr的循环引用问题:
循环引用问题模型:
问题描述:
如果有一个类A和类B,其数据成员是一个shared_ptr指向彼此。那么此时类A和类B的引用计数ref为1。如果此时又有两个智能指针分别指向A和B,那么此时类A和类B的引用计数为2,当这两个智能指针离开其作用域的时候ref减为1,但并不会释放智能指针所指向的对象。会造成内存泄漏。
#include <iostream> #include <memory> using namespace std; class B; class A { public: ~A() { cout << "A delete" << endl; } shared_ptr<B> ptr; }; class B { public: ~B() { cout << "B delete" << endl; } shared_ptr<A> ptr; }; int main() { while(1) { shared_ptr<A> pa(new A()); //A对象的引用计数ref=1 shared_ptr<B> pb(new B()); //B对象的引用计数ref=1 pa->ptr = pb; //B对象的引用计数ref=2 pb->ptr = pa; //A对象的引用计数ref=2 } //离开作用域后,虽然pa和pb智能指针对象释放了,但由于其所指对象的引用计数为1而未被释放,故造成内存泄漏。 }
weak_ptr的出现:
为了解决循环引用的问题,出现了弱引用的weak_ptr。weak_ptr指向对象并不会对引用计数+1。weak_ptr不对其所指的对象进行内存资源的管理。解决循环引用的方法就是将shared_ptr的数据成员改为weak_ptr。
weak_ptr的用法:
当创建一个weak_ptr时,要用一个shared_ptr来初始化它:
shared_ptr<int> p = make_shared<int>(111); weak_ptr wp(p);
因为是弱引用,创建wp不会改变p的引用计数。有可能weak_ptr所指向的对象不存在了,因此无法直接通过weak_ptr指针访问其所指向的对象,应该通过调用lock()方法将weak_ptr提升为一个shared_ptr,再访问其所指向的对象。如果提升失败那么指向的对象已被释放。
if (shared_ptr<int> p = wp.lock()) { //..... }