C++智能指针详解【C++智能指针】
- 自動內存管理
- 智能指針
- 什么是 RAII
- 原理
- 智能指針的模板(template)實現
- auto_ptr
- auto_ptr 使用
- 重載函數 operator-> / *語法格式
- 自實現 auto_ptr
- auto_ptr被廢棄的原因
- unique_ptr
- unique_ptr使用
- 常用接口
- shared_ptr
- 原理
- 常用接口
- 移動和傳遞引用
- 小結
- unique_ptr可用于托管堆內存數組。(官方僅支持unique_ptr)
- shared_ptr不可用于托管堆內存數組。(官方不支持)
自動內存管理
智能指針
一套成熟的類庫,通常都會引用內存管理機制。
例如:Cocos,Qt等
C++在語言層面也提供了內存管理機制,即智能指針 auto_ptr。
雖然 auto_ptr 己經 deserted 了(引自 Google C++ 編程規范),但是它的后繼者,諸如 share_ptr, weak_ptr 靈感均取自于auto_ptr。
什么是 RAII
RAII(Resource Acquisition Is Initialization),也稱為"資源獲取即初始化",是 C++語言的一種管理資源、避免泄漏的慣用法。
RAII是一種利用對象生命周期來控制程序資源的簡單技術。
程序資源包括:內存、文件句柄、網絡連接、互斥量等等。
C++中的構造器和析構器就是根據RAII思想設計。
C++標準保證任何情況下,已構造的對象最終會銷毀,即它的析構函數最終會被調用。
有構造就一定有析構。
RAII一般做法:
簡單的說,RAII 的做法是使一個對象,在其構造時獲取資源,在對象生命期控制對資源的訪問使之始終保持有效,最后在對象生命期結束時析構進行資源釋放。
注意理解:在類對象包裝其他對象資源時,通過NEW獲取其他對象資源,通過構造進行其他對象資源初始化,通過析構在其他對象消失時進行資源釋放。
其實也就是把管理一份資源的責任托管給了一個對象。
好處:
①:不需要顯示地釋放資源。
②:對象所需的資源在生命周期內始終保持有效。
所托管的資源,隨對象的創建而獲取,隨對象的消失而消失。
也就是所謂RAII思想:資源獲取即初始化。
原理
代理了被托管的對象指針,管理對象的生命周期,即實現自動釋放。
其行為類似于所托管的對象指針,原因是重載了operator->和operator*。
智能指針的模板(template)實現
template <class T> class SmartPtr { public:explicit SmartPtr(T* pointee) : pointee_(pointee){}~SmartPtr(){delete pointee_;}T& operator*() const{//...return *pointee_;}T* operator->() const{//...return pointee_;}private:T* pointee_;//... };auto_ptr
auto_ptr 使用
代碼演示:
#include <iostream> #include <memory>class A { public:A(){std::cout << "A()" << std::endl; //構造器}~A(){std::cout << "~A()" << std::endl; //析構器}void func(){std::cout << "hahaha" << std::endl; //類內成員函數}};void foo() {A* p = new A; //new 資源獲得:會直接調用構造器進行初始化。delete p; //delete 資源釋放:需要手動釋放。//一旦申請資源過多,業務邏輯代碼比較復雜的時候,在釋放的時候就可能會漏掉。std::auto_ptr<A> pp(new A);//資源獲取即初始化。// //上面//pp(new A):括號內部就是資源獲取,并且會直接調用pp對象的構造器進行初始化。//也就是new進行資源獲取后就直接進行初始化。 理解:資源獲取即初始化。//按照使用指針的方式使用pp->func();(*pp).func();//重點:離開作用域,對象生命周期結束,會自動釋放對象資源。 }int main() {foo();return 0; }運行結果:
我們可以看到上面使用智能指針,new申請空間之后自動調用構造器初始化并且在對象結束后自動調用析構器釋放申請的內存空間。也就是自動調用構造器和析構器。
重載函數 operator-> / *語法格式
類名& operator * ()
類名 * operator->()
自實現 auto_ptr
代碼演示:
#include <iostream> #include <memory>class A { public:A(){std::cout << "A()" << std::endl; //構造器初始化資源}~A(){std::cout << "~A()" << std::endl; //析構銷毀資源}void func(){std::cout << "hahaha" << std::endl; //類內成員函數}};class myauto_ptr //管理資源A的智能指針 { public:myauto_ptr(A* ptr) //構造器初始化資源{_ptr = ptr;}~myauto_ptr() //析構銷毀資源{delete _ptr;}A* operator->() //->運算符重載{return _ptr;}A & operator*() //*運算符重載{return *_ptr;}private:A* _ptr; };void foo() {myauto_ptr sp(new A);sp->func();(*sp).func(); }int main() {foo();return 0; }運行結果:
上面實現了:
類對象包裝其他對象資源時,通過NEW獲取其他對象資源,通過構造進行其他對象資源初始化,通過析構在其他對象消失時進行資源釋放。
類比上面代碼中: myauto_ptr 類對象包裝 A 類對象資源時,myauto_ptr 類對象通過NEW獲取A 類對象資源,通過myauto_ptr 類對象構造進行 A 類對象資源初始化,通過myauto_ptr 類對象析構在 A 類對象資源消失時進行資源釋放。
. 運算符不可重載。
通過重載運算符 * 和 -> ,就可以將對象像指針一樣使用。
auto_ptr被廢棄的原因
防止兩個 auto_ptr 對象擁有同一個對象(一塊內存)。
例如:ap 與 ap2 都覺得指針 p 是歸它管。在析構時都試圖刪除 p ,兩次刪除同一個對象的行為在C++標準中沒有定義。
代碼演示:
#include <iostream> #include <memory>int main() {int* p = new int(10);{std::auto_ptr<int> ap(p); //使用ap管理pstd::cout << *ap << std::endl;}std::auto_ptr<int> ap2(p); //使用ap2管理pstd::cout << *ap2 << std::endl;return 0; }運行結果:
編譯器報錯。
作參數傳遞的時,亦是會出現同樣的情況,兩個指針,對同一段資源產生了引用行為。
基本類型代碼演示:
運行結果:
自定義類類型代碼演示:
運行結果:
析構發生在func(apc);運行結果之后。
apc進入func();就是函數內的臨時變量,在函數運行結束之后,對于apc進行析構。
main函數運行結束時,ap再次析構編譯器就會報錯。
不允許兩個auto_ptr指針指向相同的對象。
unique_ptr
uniqu_ptr 的使用方法基本上等同于auto_ptr, 不同之處就在于實現了資源的唯一。既不可以拷貝也不可以賦值,正如其名字一樣。
unique_ptr使用
代碼演示:
#include <iostream> #include <memory> using namespace std;class Copy { public:Copy() :_i(){cout << "Copy()" << endl;}Copy(int i) :_i(new int(i)){cout << "Copy(int i)" << endl;}Copy(const Copy& another):_i(new int(*another._i)){cout << " Copy(const Copy & another)" << endl;}~Copy() {cout << "~Copy()" << endl;}int* _i; };void func(unique_ptr<Copy> upc) {}int main(int argc, char* argv[]) {unique_ptr<Copy> upc(new Copy(10));cout << *upc->_i << endl;unique_ptr<Copy> upc2(upc); //編譯不過func(upc); //編譯不過return 0; }編譯器報錯:
常用接口
生命周期隨構造者,reset 自動析構再重新構造,get 判斷是否有效、支持放在容器內;真正意義智能指針。
不論是臨時變量指針、類成員指針變量…90%的指針都應該用這個。
代碼演示:
運行結果:
shared_ptr
原理
unique_ptr 解決了auto_ptr 中引用同一個對象的問題,方式就是不可引用同一個對象。
shared_ptr 解決了auto_ptr 中引用同一個對象共同擁有一個資源,但不會重析構的問題。
shared_ptr 原理是在內部保持一個引用計數,并且僅當引用計數為0才能被刪除。
不可以用數組。
常用接口
可以有多個持有者的共享指針,即所謂引用計數型指針,直到最后一個持有者delete釋放時,其指向的資源才會真正被釋放。
典型應用案例:
如對同一個全局無鎖隊列對象由shared_ptr 封裝,多線程的多個持有者均持有對其的引用。直到全部線程都釋放掉對其的引用時,該無鎖隊列對象才會被最終銷毀。
shared_ptr 適合用于管理“全局動態資源”。
代碼演示:
#include <iostream> #include <memory> using namespace std;int main() {shared_ptr<int> sp1 (new int(666));cout << *sp1 << endl;cout << "cout:" << sp1.use_count() << endl;//1{shared_ptr<int> sp2(sp1);cout << *sp2 << endl;cout << "cout:" << sp1.use_count() << endl;//2cout << "cout:" << sp2.use_count() << endl;//2}cout << "cout:" << sp1.use_count() << endl;//1shared_ptr<int> sp3(sp1);cout << *sp3 << endl;cout << "cout:" << sp3.use_count() << endl;//2return 0; }運行結果:
代碼演示:
#include <iostream> #include <memory> using namespace std;class Copy { public:Copy() :_i(){cout << "Copy()" << this << endl;}Copy(int i) :_i(new int(i)){cout << "Copy(int i)" << this << endl;}Copy(const Copy & another) :_i(new int(*another._i)){cout << " Copy(const Copy & another)" << this << endl;}~Copy() {cout << "~Copy()" << this << endl;}void dis(){cout << *_i << endl;}int* _i; };void func(shared_ptr<Copy> upc) {cout << "func cout:" << upc.use_count() << endl;//3upc.reset();cout << "func cout:" << upc.use_count() << endl;//0 }int main(int argc, char* argv[]) {shared_ptr<Copy> sp1 (new Copy);cout << "cout:" << sp1.use_count() << endl;//1sp1.reset();//釋放資源//sp1.reset();//釋放資源 引用計數為0后,多次reset引用計數仍然為0。//sp1.reset();//釋放資源 引用計數為0后,多次reset引用計數仍然為0。cout << "cout:" << sp1.use_count() << endl;//0cout << "++++++++++" << endl;shared_ptr<Copy> sp2(new Copy);shared_ptr<Copy> sp3(sp2);shared_ptr<Copy> sp4(sp3);//cout << "cout:" << sp2.use_count() << endl;//3//sp2.reset(); sp2.reset(); sp2.reset(); //sp2釋放三次//cout << "cout:" << sp3.use_count() << endl;//2//重要:reset操作只管理自己身上的引用。cout << "cout:" << sp2.use_count() << endl;//3sp2.reset(); sp3.reset(); sp4.reset(); //資源的引用釋放三次cout << "cout:" << sp2.use_count() << endl;//0cout << "----------" << endl;shared_ptr<Copy> sp5(new Copy);cout << "cout:" << sp5.use_count() << endl;//1{shared_ptr<Copy> sp6(sp5);cout << "cout:" << sp5.use_count() << endl;//2cout << "cout:" << sp6.use_count() << endl;//2cout << "==========" << endl;}cout << "cout:" << sp5.use_count() << endl;//1cout << "**********" << endl;shared_ptr<Copy> sp7(sp5);cout << "cout:" << sp5.use_count() << endl;//2cout << "cout:" << sp7.use_count() << endl;//2func(sp7);cout << "cout:" << sp7.use_count() << endl;//2cout << "//" << endl;if (sp7.unique())cout << "s7 unique\n" << endl;elsecout << "s7 not unique\n" << endl;sp7.reset();cout << "cout:" << sp7.use_count() << endl;//0cout << "cout:" << sp5.use_count() << endl;//1if (sp5.unique())cout << "sp5 unique\n" << endl;elsecout << "sp5 not unique\n" << endl;return 0; }運行結果:
移動和傳遞引用
代碼演示:
#include <iostream> #include <memory> using namespace std;class Copy { public:Copy() :_i(){cout << "Copy()" << this << endl;}Copy(int i) :_i(new int(i)){cout << "Copy(int i)" << this << endl;}Copy(const Copy & another) :_i(new int(*another._i)){cout << " Copy(const Copy & another)" << this << endl;}~Copy() {cout << "~Copy()" << this << endl;}void dis(){cout << *_i << endl;}int* _i; };void func(shared_ptr<Copy> & sp) {cout << "func.use_count:" << sp.use_count() << endl;//1 }int main(int argc, char* argv[]) {//moveshared_ptr<Copy> sp2(new Copy);shared_ptr<Copy> sp3(sp2);shared_ptr<Copy> sp4(sp3);cout << "sp2.use_count:" << sp2.use_count() << endl;//3shared_ptr<Copy> spm = std::move(sp2); cout << "spm.use_count:" << spm.use_count() << endl;//3cout << "sp3.use_count:" << sp3.use_count() << endl;//3cout << "sp4.use_count:" << sp4.use_count() << endl;//3//移動不會導致引用計數增加。shared_ptr<Copy> sp5(new Copy);cout << "sp5.use_count:" << sp5.use_count() << endl;//1func(sp5);cout << "sp5.use_count:" << sp5.use_count() << endl;//1//傳遞shared_ptr的引用不會使引用計數+1return 0; }運行結果:
小結
unique_ptr可用于托管堆內存數組。(官方僅支持unique_ptr)
代碼演示:
#include <iostream> #include <memory>using namespace std;class A { public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}void dis(){cout << "A::void dis()" << endl;} };int main() {unique_ptr<int[]> up(new int[5]{ 1,2,3,4 });for (int i = 0; i < 5; i++)cout << up[i] << endl;unique_ptr<A[]> up2(new A[5]);for (int i = 0; i < 5; i++)up2[i].dis();return 0; }運行結果:
shared_ptr不可用于托管堆內存數組。(官方不支持)
總結
以上是生活随笔為你收集整理的C++智能指针详解【C++智能指针】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xml格式解析
- 下一篇: operator new/delete,