c++11:智能指针
我們在程序運行的過程中,經(jīng)常出現(xiàn)段錯誤、內(nèi)存持續(xù)增大等,是C++顯式內(nèi)存管理存在的問題,主要歸納為以下幾點:
針對以上1~3的問題,C++標準中提供了智能指針來解決。
智能指針是基于RAII(Resource Acquisition Is Initialization)機制實現(xiàn)的類(模板),具有指針的行為(重載了operator*與operator->操作符)。當對象創(chuàng)建的時候,進行初始化;離開其作用域后,通過自動調(diào)用析構函數(shù)釋放資源。
C++98中,智能指針通過一個模板類型"auto_ptr"來實現(xiàn),auto_ptr對象通過初始化指向由new創(chuàng)建的動態(tài)內(nèi)存,當auto_ptr對象生命周期結
束時,其析構函數(shù)會將auto_ptr對象擁有的動態(tài)內(nèi)存自動釋放。即使發(fā)生異常,通過異常的棧展開過程也能將動態(tài)內(nèi)存釋放。但其有一些缺點:
- 賦值和拷貝操作的目標對象會先釋放其原來所擁有的對象
auto_ptr<int> ap1(new int(100)); ????auto_ptr<int> ap2(ap1); ????// ap1 == nullptr; auto_ptr 不能用在stl容器中,因為stl容器要求存儲的類型必須為值語義類型。即兩個對象在拷貝或賦值之后相等(ap1 == ap2)
- auto_ptr 析構的時候調(diào)用的是delete,所以不能用auto_ptr管理數(shù)組指針
~auto_ptr() { delete _M_ptr; }
因此,在C++11標準中,改用unique_ptr、shared_ptr及weak_ptr等智能指針來對動態(tài)內(nèi)存進行管理。auto_ptr為了兼容以前的代碼被遺留下來,不建議使用。
頭文件
<memory>
命名空間為
?std
unique_ptr
概念:
- unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現(xiàn)std::move())。* unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現(xiàn)std::move())。
- unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時開始,直到離開作用域。離開作用域時,若其指向?qū)ο?#xff0c;則將其所指對象銷毀(默認使用delete操作符,用戶可指定其他操作)。
- unique_ptr指針與其所指對象的關系:在智能指針生命周期內(nèi),可以改變智能指針所指對象,如創(chuàng)建智能指針時通過構造函數(shù)指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉(zhuǎn)移所有權。
基本用法:
| #include <iostream> #include <memory> #include <vector> using namespace std; struct Foo { ????Foo() {} ????~Foo() {} ????void Print() { cout << "Foo" << endl; } }; int main(void) { ????Foo* p1 = new Foo(); ????unique_ptr<Foo> up1;?????? // up1==nullptr //? up1 = p1;????????????????? // 編譯錯誤,不支持這樣賦值 ????up1.reset(p1);???????????? // 替換管理對象,并釋放之前管理的對象 ????p1 = nullptr; //? unique_ptr<Foo> up2(up1);? // 編譯錯誤,不支持這樣構造 ????unique_ptr<Foo> up2(std::move(up1)); // up1所有權轉(zhuǎn)移到up2。up1==nullptr ????up1.swap(up2);???????????? // up2與up1管理對象的指針交換。 up2==nullptr ????if (up1) {???????????????? // up1 != nullptr ????????up1->Print();????????? // unique_ptr重載了-> ????????(*up1).Print();??????? // unique_ptr重載了* ????} //? up2->Print();????????????? // 錯誤 up2 == nullptr, 必須先判斷再調(diào)用 ????p1 = up1.get();??????????? // get() 返回所管理對象的指針, up1繼續(xù)持有其管理權 ????p1 = up1.release();??????? // release() 返回管理對象的指針,并釋放管理權,up1==nullptr ????delete p1; ????unique_ptr<Foo> up3(new Foo()); ????up3.reset();??????????????? // 顯示釋放釋放管理對象的內(nèi)存,也可以這樣做:up = nullptr; ????vector<unique_ptr<Foo>> v; ????unique_ptr<Foo> up4(new Foo()); //? v.push_back(up4);??????????? // 編譯錯誤,不支持這樣拷貝 ????v.push_back(std::move(up4);? // 只能up4放棄對其所有權,通過std::move()將所有權轉(zhuǎn)移到容器中 ????return 0; } |
應用場景:
- 只要unique_ptr智能指針創(chuàng)建成功,其析構都會被調(diào)用,確保動態(tài)資源的釋放------避免內(nèi)存泄漏。
- 把unique_ptr作為引用參數(shù),傳遞給其他例程,不用擔心該指針在例程中被copy一份,或不小心釋放掉。
shared_ptr
概念:
- shared_ptr 基于“引用計數(shù)”模型實現(xiàn), 多個shared_ptr對象可以擁有同一個動態(tài)對象,并維護了一個共享的引用計數(shù)。當最后一個指向該對象的shared_ptr被銷毀或者reset時,會自動釋放其所指的對象,回收動態(tài)資源。
- 銷毀該對象時,使用默認的delete/delete[]表達式,或者是在構造 shared_ptr 時傳入的自定義刪除器(deleter),以實現(xiàn)個性化的資源釋放動作。
基本用法:
| #include <iostream> #include <memory> #include <vector> #include <assert.h> using namespace std; struct Foo { ????int v; }; int main(void) { ????shared_ptr<Foo> sp1(new Foo{10}); ????cout << sp1.unique() << endl;??? // 1 當前shared_ptr唯一擁有Foo管理權時,返回true,否則返回false ????cout << sp1.use_count() << endl; // 1 返回當前對象的引用計數(shù) ????shared_ptr<Foo> sp2(sp1); ????assert(sp1->v == sp2->v);??????? // sp1與sp2共同擁有Foo對象 ????cout << sp2.unique() << endl;??? // 0 false ????cout << sp2.use_count() << endl; // 2 ????sp1.reset();???????????????????? // 釋放對Foo的管理權,同時引用計數(shù)減1 ????assert(sp1 == nullptr);????????? // sp1 為空 ????cout << sp1.unique() << endl;??? // 0 不會拋出異常 ????cout << sp1.use_count() << endl; // 0 不會拋出異常 ????cout << sp1.get() << endl;?????? // 0 不會跑出異常 //? cout << sp1->v << endl;????????? // 執(zhí)行錯誤 sp1為nullptr時,operator* 和 operator-> 都會導致未定義行為 ????cout << sp2.unique() << endl;??? // 1 true ????sp1.swap(sp2);?????????????????? // sp1與sp2交換管理權,及引用計數(shù) ????assert(sp2 == nullptr); ????cout << (*sp1).v << endl;??????? // 10 同sp1->v相同 ????vector<shared_ptr<Foo>> vec; ????vec.push_back(sp1); ????cout << sp1.use_count() << endl; // 2 ????return 0; // vector先析構,里面存儲的對象引用計數(shù)減1,但不為0,不釋放對象 // sp1后析構,引用計數(shù)減1,變?yōu)?,釋放所指對象的內(nèi)存資源 } |
主要方法:
| T& operator*() const; T* operator->() const; T* get() const; bool unique() const; long use_count() const; void swap(shared_ptr<T>& b); |
應用場景:
? ? 1. 在一個map(unordered_map)中,多key索引一個value,使用shared_ptr。
| #include <iostream> #include <memory> #include <map> #include <cstdint> using namespace std; Class SessionNode { public: ????SessionNode() {} ????virtual ~SessionNode() {} }; typedef std::shared_ptr<SessionNode> SessionNodeSP; int main(void) { ????map<uint64_t, SessionNodeSP> map; ????uint64_t imsi = 4600000; ????uint32_t tmsi = 0x12345; ????{ ????????SessionNodeSP sp(new SessionNode()); ????????map[imsi] = sp; ????????map[tmsi] = sp; ????????cout << sp.use_count() << endl;? // 3 ????} // sp 銷毀,引用計數(shù)減1,變?yōu)? ????map.erase(tmsi);? // use_count()為1 ????map.erase(imsi);? // use_count()為0,釋放被管理對象的內(nèi)存 ????return 0; } |
? ? 2. 多個map索引同一value
| #include <iostream> #include <memory> #include <map> #include <cstdint> using namespace std; struct Node { ????uint64_t imsi; ????uint32_t tmsi; }; typedef std::shared_ptr<Node> NodeSP; class Session { public: ????Session() {} ????~Session() {} ????NodeSP GetNode(uint64_t imsi, uint32_t tmsi) { ????????NodeSP sp(new Node{imsi, tmsi}); ????????imsi_map_[imsi] = sp; ????????tmsi_map_[tmsi] = sp; ????????return sp; ????} ????void EraseNode(NodeSP& sp) { ????????if (sp == nullptr) ????????????return; ????????imsi_map_.erase(sp->imsi); ????????imsi_map_.erase(sp->tmsi); ????} private: ????map<uint64_t, NodeSP> imsi_map_; ????map<uint32_t, NodeSP> tmsi_map_; }; int main(void) { ????Session ses; ????uint64_t imsi = 4600000; ????uint32_t tmsi = 0x12345; ????NodeSP sp; ????sp = ses.GetNode(imsi, tmsi); ????cout << sp.use_count() << endl; // 3 ????// ... do something with sp ????ses.EraseNode(sp); ????cout << sp.use_count() << endl; // 1 ????sp.reset(); // 主動釋放被管理對象內(nèi)存 ????return 0; } |
? ? 3. 防止裸指針被刪除
| #include <iostream> #include <memory> class Cdr? { public: ????Cdr() {} protected: ????virtual ~Cdr() {} }; class SignalCdr : public Cdr { public: ????SignalCdr() {} ????virtual ~SignalCdr() {} }; typedef std::shared_ptr<Cdr> CdrSP; CdrSP CreateSignalCdr() { ????CdrSP sp(new SignalCdr()); ????return sp; }; int main(void) { ????CdrSP sp_cdr = CreateSignalCdr(); ????SignalCdr* p_signal_cdr = reinterpret_cast<SignalCdr*>(sp_cdr.get()); ????// ... do something //? delete p_signal_cdr; // 執(zhí)行錯誤,~Cdr()為protected ????return 0; } |
? ? 4. 定制刪除器。
| #include <iostream> #include <memory> #include <errno.h> #include <stdio.h> #include <string.h> using namespace std; class FileCloser { public: ????void operator()(FILE* p) { ????????// ... do something ????????cout << "close file" << endl; ????????fclose(p); ????} }; int main(void) { ????try { ????????FILE* p_file = fopen("./test.cpp", "r"); ????????if (p_file == nullptr) ????????????throw errno; ????????shared_ptr<FILE> sp_file(p_file, FileCloser()); //????? shared_ptr<FILE> sp_file(p_file, &fclose);? //如果只需要調(diào)用一個單參數(shù)的函數(shù),可以直接這么寫 ????????// ... do something ????} catch (int& err) { ????????cout << strerror(err) << endl; ????} ????return 0; } |
? ? 5. 在引起循環(huán)引用的時候使用weak_ptr。
| #include <iostream> #include <memory> using namespace std; struct Husband; struct Wife; typedef std::shared_ptr<Husband> HusbandSP; typedef std::shared_ptr<Wife>??? WifeSP; struct Wife { ????~Wife() { cout<< "wife distroy" << endl; } ????HusbandSP sp_hb; }; struct Husband { ????~Husband() { cout<< "husband distroy" << endl; } ????WifeSP sp_wf; }; int main(void) { ????{ ????????HusbandSP husband(new Husband()); ????????WifeSP??? wife(new Wife()); ????????husband->sp_wf = wife; ????????wife->sp_hb = husband; ????} // husband 和 wife,相互引用,離開作用域時引用計數(shù)都為1,造成內(nèi)存泄露 ????return 0; } |
weak_ptr
概念:
- weak_ptr是為了配合shared_ptr而引入的一種智能指針,它只能夠通過shared_ptr或者weak_ptr來構造。
- weak_ptr是作為shared_ptr的”觀察者“,并不修改shared_ptr所管理對象的引用計數(shù),當shared_ptr銷毀時,weak_ptr會被設置為空,所以使用weak_ptr比底層指針的好處在于能夠知道所指對象是否有效
- weak_ptr不具有普通指針的行為,因為沒有重載operator*和->。所以當weak_ptr觀察的對象存在,并且需要修改其內(nèi)容時,需要提升為shared_ptr來操作。
基本用法:
| #include <iostream> #include <memory> using namespace std; struct Cdr{ ????int v; }; typedef std::shared_ptr<Cdr> CdrSP; typedef std::weak_ptr<Cdr>?? CdrWP; void UpdateCdr(CdrWP wp) { ????if (wp.expired() == false) {?? // 檢查被管理對象是否被刪除,true 刪除,false 沒被刪除;比use_count()==1要快 ????????CdrSP sp = wp.lock();????? // 提升為強引用 //????? CdrSP sp(wp);????????????? // 另一種wp提升為強引用方法 ????????if (sp != nullptr) {?????? // 若提升失敗,shared_ptr 為 nullptr,此例子不會失敗 ????????????sp->v *= 2; ????????????cout << sp.use_count() << endl; // 此時引用計數(shù)為2 ????????} ????} // sp刪除,引用計數(shù)減1 ????wp.reset(); // 顯示釋放所有權,或者等離開作用域會自動釋放 } int main(void) { ????CdrSP sp(new Cdr{10}); ????UpdateCdr(sp);? // 對sp進行操作 ????cout << sp->v << endl;? // 20 ????return 0; } |
主要方法:
| long use_count() const; bool expired() const; std::shared_ptr<T> lock() const; |
應用場景:
| #include <iostream> #include <memory> #include <map> using namespace std; struct Cdr { ????int v; }; typedef std::shared_ptr<Cdr> CdrSP; typedef std::weak_ptr<Cdr>?? CdrWP; class Cache { public: ????Cache() {} ????~Cache() {} ????void UpdateIndex(CdrSP& sp) { ????????if (sp != nullptr && sp->v > 0) ????????????cdr_map_[sp->v] = sp; ????} private: ????map<int, CdrWP> cdr_map_; }; int main(void) { ????Cache cache; ????CdrSP sp_cdr(new Cdr{1}); ????cache.UpdateIndex(sp_cdr); ????cout << sp_cdr.use_count() << endl; // 1 ????return 0; // sp_cdr銷毀,引用計數(shù)為1,釋放管理對象內(nèi)存 // cache銷毀,因為map中存儲的為weak_ptr,weap_ptr為空,所以不會產(chǎn)生二次釋放 } |
bad_weak_ptr異常捕獲
當shared_ptr通過weak_ptr參數(shù)構造,而weak_ptr指向一個已經(jīng)被刪除的對象時,會拋出std::bad_weak_ptr異常。
| #include <iostream> #include <memory> using namespace std; int main() { ????shared_ptr<int> sp1(new int(1)); ????weak_ptr<int> wp(sp1); ????p1.reset(); ????try { ????????shared_ptr<int> sp2(wp); ????} catch (const std::bad_weak_ptr& e) { ????????cout << e.what() << endl;??? // "std::bad_weak_ptr" ????} } |
從this創(chuàng)建shared_ptr
有時候,需要從this獲得 shared_ptr ,即是說,你希望你的類被shared_ptr所管理,你需要把"自身"轉(zhuǎn)換為shared_ptr的方法。
| #include <iostream> #include <memory> using namespace std; class Foo; typedef std::shared_ptr<Foo> FooSP; void DoSomething(const FooSP& sp) { ????cout << sp.use_count() << endl; // 2 } class Foo : public std::enable_shared_from_this<Foo> { public: ????Foo() {} ????~Foo() {} ????void Do() { ????????DoSomething(shared_from_this()); ????} }; int main(void) { ????FooSP sp(new Foo()); ????cout << sp.use_count() << endl; // 1 ????sp->Do(); ????return 0; } |
總結:
- unique_ptr/shared_ptr中,get()把底層指針暴露出來,為的是兼容老程序,一般不提倡使用,因為很難確保別的例程會對這個指針做什么,比如說delete/delete[]。
- 作為shared_ptr和weak_pt作為參數(shù)傳遞時,使用引用傳遞以減小開銷。
- weak_ptr 沒有重載operator==,所以不能比較。shared_ptr可以比較,比較的是里面底層指針。
- auto_ptr、unique_ptr、shared_ptr,不能這樣用
{ ????int* p = new int(100); ????auto_ptr<int> ap1(p); ????auto_ptr<int> ap2(p); } 當離開作用域,ap1和ap2都試圖刪除p,會造成double free。
- 使用智能指針雖然不需要手動處理引用計數(shù)和調(diào)用delete來釋放資源。但要清楚什么時候資源會被釋放。
例如:用容器來存儲shared_ptr,并且從容器中刪除的時候釋放資源,那么其他例程在使用shared_ptr時只是更新其資源;
如果從容器中刪除shared_ptr時不釋放資源,那么應該被另外一個shared_ptr所共享;這個時候容器中存儲weak_ptr更合適。
引用:
英文網(wǎng)址:
http://en.cppreference.com/w/cpp/memory
中文網(wǎng)址(google 機器翻譯):
http://zh.cppreference.com/w/cpp/memory
轉(zhuǎn)載于:https://www.cnblogs.com/457220157-FTD/p/4129058.html
總結
以上是生活随笔為你收集整理的c++11:智能指针的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做梦梦到你了怎么回复
- 下一篇: linux下查看文件及目录个数