- 1.往期回顾
- 2.为什么要使用shared_ptr
- 3.shared_ptr的使用
- 1.shared_ptr原理
- 2.shared_ptr的使用
- 4.使用shared_ptr的陷阱
- 5.“死锁”的解除——使用weak_ptr
C++智能指针总结一——auto_ptr
C++智能指针总结二——unique_ptr
2.为什么要使用shared_ptr这是因为,无论是auto_ptr还是unique_ptr都是排它型的,即只允许一个智能指针对象引用控制一块动态内存。那如果我们要使用多个指向同一块动态内存的指针时,显然auto_ptr与unique_ptr都不能做到。因此,为了实现多个智能指针引用同一块动态内存的目的,C++标准新增了shared_ptr。
3.shared_ptr的使用 1.shared_ptr原理shared_ptr内部实现了一个计数器,用于记录引用同一块动态内存的智能指针数量,一旦有新的智能指针指向了已被引用的动态内存,引用计数加一。一旦有智能指针生命周期到了调用析构函数销毁时,引用计数减一,如果检测到此时引用计数为零,则销毁智能指针的同时释放动态内存。
看一个例子:
#include#include #include #include #include using namespace std; class DEMO { public: DEMO(int data) { this->data = data; cout << "创建text" << endl; cout << "data=" << data << endl; } ~DEMO() { cout << "析构text" << data << endl; } private: int data; }; int main(void) { { shared_ptr ptr1(new DEMO(1)); //引用计数加一 { shared_ptr ptr2(ptr1); //引用计数加一 { shared_ptr ptr3(ptr2); //引用计数加一 { shared_ptr ptr4(ptr3); //引用计数加一 cout << "ptr4.count=" << ptr4.use_count() << endl; } cout << "ptr3.count=" << ptr3.use_count() << endl; //引用计数减一 } cout << "ptr2.count=" << ptr2.use_count() << endl; //引用计数减一 } cout << "ptr1.count=" << ptr1.use_count() << endl; //引用计数减一 } //引用计数减一 system("pause"); return 0; }
结果:
shared_ptr ptr2(ptr1),即使用ptr1初始化ptr2 。
.use_count()方法可以返回智能指针的引用计数。
开始一共有四个指针指向同一块动态内存,所以最开始引用变量是4,而后随着指针生命周期的结束,引用变量逐渐减一,直至引用变量减为0,释放动态内存,执行DEMO对象的析构函数。
2.shared_ptr的使用主要补充一些shared_ptr新增的API,至于与auto_ptr,unique_ptr一样的方法,就不赘述了。
1.构造
#include#include #include #include #include using namespace std; int main(void) { shared_ptr ptr1; //初始化为空的shared_ptr,可以指向int类型的对象 shared_ptr ptr2; //初始化为空的shared_ptr,可以指向int类型的数组 shared_ptr ptr3(new int[5]{ 1,2,3,4,5 }); //使用参数列表初始化动态数组,C++17后支持 system("pause"); return 0; }
2.使用make_shared 初始化对象
#include#include #include #include #include using namespace std; int main(void) { shared_ptr ptr1 = make_shared (5); system("pause"); return 0; }
推荐使用make_shared初始化对象,因为效率更高。
使用shared_ptr初始化与赋值要明白引用计数的变化,其实只要看有几个智能指针对象指向了同一块内存,引用计数就是几。
4.使用shared_ptr的陷阱先看一段代码:
#include#include #include #include #include using namespace std; class GRIL; //创建BOY类 class BOY { public: BOY() { cout << "执行BOY类的构造方法" << endl; } ~BOY() { cout << "执行BOY类的析构方法" << endl; } void GetGril(shared_ptr & gril_ptr) { //BOY获取GRIL方法 gril = gril_ptr; } private: shared_ptr gril; //BOY类里的shared_ptr指针 }; //创建GRIL类 class GRIL { public: GRIL() { cout << "执行GRIL类的构造方法" << endl; } ~GRIL() { cout << "执行GRIL类的析构方法" << endl; } void GetBoy(shared_ptr & boy_ptr) { //GRIL类获取BOY方法 boy = boy_ptr; } private: shared_ptr boy; //GRIL类里的shared_ptr指针 }; int main(void) { { shared_ptr ptr1(new BOY); //分配一个boy类对象 shared_ptr ptr2(new GRIL); //分配一个gril类对象 ptr1->GetGril(ptr2); ptr2->GetBoy(ptr1); } system("pause"); return 0; }
执行结果为:
从结果可以看到,动态分配的BOY与GRIL类对象并未被释放,这是为什么呢?
其实这是由于BOY类与GRIL类对象里的shared_ptr指针相互引用造成的。看一张图:
动态创建完BOY类与GRIL类的对象后,它们各自的智能指针的引用计数为一,又因为BOY类与GRIL类里各有一个指向对方的shared_ptr指针,执行完它们各自的.GetBoy()与.GetGril()方法后。会形成如上图的关系。此时它们各自的智能指针引用计数为二。
当shared_ptr1与shared_ptr2的生命周期到了,被销毁后,会变成如下图的关系:
此时引用计数为一不为零,所以任何一个动态内存都无法释放,其中一个无法释放,就导致另一个无法释放,就造成了类似于死锁的情况。两个都不能释放。就造成了内存泄漏。
再补充一种无法释放的情景:
#include#include #include #include #include using namespace std; //创建BOY类 class BOY { public: shared_ptr boy1; BOY() { cout << "执行BOY类的构造方法" << endl; } ~BOY() { cout << "执行BOY类的析构方法" << endl; } }; int main(void) { shared_ptr ptr1(new BOY); ptr1->boy1 = ptr1; system("pause"); return 0; }
结果:
这种情况同样会造成内存泄漏。
总结来说无法释放内存的原因是,由于对象是动态分配的,而对象本身又含有shared_ptr指针,释放对象需要shared_ptr的释放使引用计数减为零,而shared_ptr的释放又需要对象的释放,两者互相等待对方先释放,往往是两者都无法释放。
5.“死锁”的解除——使用weak_ptrweak_ptr指针——弱指针,它是为了解决shared_ptr的弊端而被设计出来辅助shared_ptr完成工作的指针。它不能使用 * 与 -> 运算符。但它可以通过lock 获得一个可用的 shared_ptr 对象。weak_ptr不会引起引用计数的变化。除非它通过lock 获得一个可用的 shared_ptr 对象,这时引用计数会加一,shared_ptr被销毁后,引用计数会减一。
看一个例子:
#include#include #include #include #include using namespace std; //创建BOY类 class BOY { public: weak_ptr boy1; BOY() { cout << "执行BOY类的构造方法" << endl; } ~BOY() { cout << "执行BOY类的析构方法" << endl; } void demo() { cout << "count=" << boy1.use_count() << endl; shared_ptr tmp = boy1.lock(); //当需要使用引用动态内存时,可使用.lock()方法构造一个临时的shared_ptr,因为是临时变量,用完后会析构掉。 cout << "count=" << tmp.use_count() << endl; } }; int main(void) { shared_ptr ptr1(new BOY); ptr1->boy1 = ptr1; ptr1->demo(); system("pause"); return 0; }
结果:
当weak_ptr需要使用引用动态内存时,可使用.lock()方法构造一个临时的shared_ptr,此时引用变量会加一。因为是临时变量,用完后会析构掉。计数减一。使用weak_ptr可以避免shared_ptr导致的动态内存无法释放的问题。



