C++ unique_ptr、shared_ptr、weak_ptr的區(qū)別小結(jié)
unique_ptr
所有權(quán)轉(zhuǎn)移
unique_ptr不能被拷貝和用于賦值,因?yàn)閡nique_ptr刪掉了這兩個(gè)函數(shù)
但是底層源碼重載了傳右值的拷貝構(gòu)造
所以可以通過(guò)std::move來(lái)通過(guò)轉(zhuǎn)移所有權(quán)
unique_ptr<Data> p6(new Data()); //不可復(fù)制構(gòu)造和賦值復(fù)制 //unique_ptr<Data>p7 = p6; 錯(cuò)誤 //p6釋放所有權(quán) 轉(zhuǎn)移到p7 unique_ptr<Data>p7 = move(p6); unique_ptr<Data>p8(new Data()); p7 = move(p8);//重新移動(dòng)賦值,原有的p6會(huì)被釋放掉 //重置空間,原空間清理 p7.reset(new Data());
所有權(quán)釋放
注意!當(dāng)unique_ptr釋放所有權(quán)以后智能指針就不會(huì)再管理這塊空間,需要自己手動(dòng)釋放空間!
//釋放所有權(quán) unique_ptr<Data>p9(new Data()); auto ptr9 = p9.release();//注意,release釋放所有權(quán)以后要自己清理空間 delete ptr9;//?。。。。。?
make_unique 和直接(new)unique_ptr
區(qū)別一 :需要一次性處理多個(gè)資源分配的地方make_unique比unique_ptr更安全
void process_data( std::unique_ptr<Data> p1, std::unique_ptr<Data> p2 ); process_data( std::unique_ptr<Data>(new Data("A")), // 分配資源 A std::unique_ptr<Data>(new Data("B")) // 分配資源 B );
編譯器在構(gòu)造函數(shù)參數(shù)時(shí),?執(zhí)行順序是不確定的??赡艿膱?zhí)行順序例如:
- new Data(“A”) → 成功,得到一個(gè)裸指針 A
- new Data(“B”) → 成功,得到一個(gè)裸指針 B*
- 構(gòu)造unique_ptr 接管 A*
- 構(gòu)造 unique_ptr 接管 B*
如果中間發(fā)生異常:
- new Data(“A”) → 成功,得到 A*
- new Data(“B”) → ?拋出異常(例如內(nèi)存不足)? ?此時(shí) A* 尚未被 unique_ptr 接管!? 異常被拋出后,裸指針 A* 無(wú)法被自動(dòng)釋放 → ?內(nèi)存泄漏。
如果選用make_unique
process_data( std::make_unique<Data>("A"), // 直接構(gòu)造并接管資源 A std::make_unique<Data>("B") // 直接構(gòu)造并接管資源 B );
此時(shí)每一步的執(zhí)行:
- make_unique(“A”) → ?立即構(gòu)造對(duì)象并封裝到 unique_ptr,無(wú)裸指針暴露
- make_unique(“B”) → 同上 如果 make_unique(“B”) 拋出異常:
?make_unique(“A”) 已經(jīng)返回的 unique_ptr 會(huì)正常析構(gòu) → 資源 A 被自動(dòng)釋放沒(méi)有泄漏!
區(qū)別二 :直接new支持自定義刪除器
new支持在構(gòu)造 unique_ptr 時(shí)指定自定義刪除器
std::unique_ptr<MyClass, Deleter> p(new MyClass, custom_deleter);
但是make_unique不支持,只能使用默認(rèn)的 delete 操作符。
shared_ptr
shared_ptr智能指針指向同一個(gè)對(duì)象的不同成員
sc2和sc3 分別 指向sc1的index1成員和index2成員,使sc1的引用計(jì)數(shù)+2
class Data { public: Data() { cout<< "Begin Data" << endl; } ~Data() { cout<< "End Data" << endl; } int index1 = 0; int index2 = 0; }; { shared_ptr<Data>sc1(new Data); //打印引用計(jì)數(shù) = 1 cout << "sc1.use_count() = " << sc1.use_count() << endl; shared_ptr<int>sc2(sc1,&sc1->index1);//引用計(jì)數(shù)+1 shared_ptr<int>sc3(sc1, &sc1->index2);//引用計(jì)數(shù)+1 //打印引用計(jì)數(shù) = 3 cout << "sc1.use_count() = " << sc1.use_count() << endl; }
循環(huán)引用問(wèn)題
當(dāng)兩個(gè)或多個(gè)對(duì)象通過(guò) shared_ptr ?互相持有對(duì)方時(shí),它們的引用計(jì)數(shù)永遠(yuǎn)不會(huì)歸零,導(dǎo)致內(nèi)存無(wú)法釋放。
class A { public: A() { cout << "Create A" << endl; } ~A() { cout << " Drop A " << endl; } void Do() { cout << "Do b2.use_count() = " << b2.use_count() << endl; auto b = b2.lock(); //復(fù)制一個(gè)shared_ptr 引用計(jì)數(shù)加一 cout << "Do b2.use_count() = " << b2.use_count() << endl; } shared_ptr<B> b1;//強(qiáng)智能指針 weak_ptr<B> b2; //弱智能指針 }; class B { public: B() { cout << "Create B" << endl; } ~B(){ cout << " Drop B " << endl; } shared_ptr<A> a1;//強(qiáng)智能指針 weak_ptr<A> a2; //弱智能指針 }; { auto a = make_shared<A>();//a引用計(jì)數(shù)+1 auto b = make_shared<B>();//b引用計(jì)數(shù)+1 a->b1 = b;//b引用計(jì)數(shù)+1 //出作用域前,引用計(jì)數(shù)為2 cout << "a->b1 = b;b.use_count()=" << b.use_count() << endl; b->a1 = a;//a引用計(jì)數(shù)+1 //出作用域前,引用計(jì)數(shù)為2 cout << "b->a1 = a;a.use_count()=" << a.use_count() << endl; }
由于調(diào)用問(wèn)題,導(dǎo)致出作用域以后a和b的引用計(jì)數(shù)都還是1,所以空間沒(méi)有被釋放
改成使用weak_ptr
{ auto a = make_shared<A>(); auto b = make_shared<B>(); a->b2 = b;//weak_ptr 引用計(jì)數(shù)不加一 a->Do();//Do函數(shù)里引用計(jì)數(shù)加一,出Do函數(shù)作用域減一 cout << "a->b2 = b;b.use_count()=" << b.use_count() << endl; b->a2 = a;//引用計(jì)數(shù)不加一 cout << "b->a2 = a;a.use_count()=" << a.use_count() << endl; }//不會(huì)產(chǎn)生循環(huán)引用問(wèn)題
為什么weak_ptr能解決shared_ptr的循環(huán)引用問(wèn)題
weak_ptr 本身不擁有資源所有權(quán):
它只是觀察 shared_ptr 管理的對(duì)象,不會(huì)增加引用計(jì)數(shù)。
所以a->b2 = b;這句不會(huì)增加b的引用計(jì)數(shù)
同理b->a2 = a;這句也不會(huì)增加a的引用計(jì)數(shù)
但是由于weak_ptr只是一個(gè)觀察者,無(wú)法訪問(wèn)任何資源,僅“觀察”資源,不擁有所有權(quán)如果想要訪問(wèn)資源該怎么辦呢?
如同a->Do();里做的那樣
只要原 shared_ptr(即 a)未釋放資源,就可以通過(guò) lock() 獲取有效的 shared_ptr 并訪問(wèn)。
void Do() { cout << "Do b2.use_count() = " << b2.use_count() << endl; auto b = b2.lock(); //返回一個(gè)shared_ptr 引用計(jì)數(shù)加一 //這里還可以做其他的訪問(wèn)shared_ptr資源的操作 cout << "Do b2.use_count() = " << b2.use_count() << endl; }//出作用域加上的那個(gè)引用計(jì)數(shù)自動(dòng)-1
通過(guò)weak_ptr.lock()來(lái) 返回 一個(gè) shared_ptr 并將引用計(jì)數(shù)加一
(注意!只有當(dāng)原shared_ptr對(duì)象還存在的時(shí)候才會(huì)返回shared_ptr,否者返回nullptr)
除了放函數(shù)里,還能放判斷條件里
if (auto a_shared = b->a_weak.lock()) { // 返回nullptr 說(shuō)明shared_ptr被釋放 //不會(huì)執(zhí)行此處 } else { //引用計(jì)數(shù)+1 std::cout << "A is already destroyed!" << std::endl; // 輸出此句 }//引用計(jì)數(shù)-1
通過(guò)這種方式,weak_ptr 可以安全地觀察資源,而不會(huì)導(dǎo)致循環(huán)引用或內(nèi)存泄漏
make_shared 和 直接 new 一個(gè)shared_ptr的區(qū)別
區(qū)別一:make_shared資源分配更安全
原因和make_unique一樣,這里就不再贅述了
區(qū)別二:直接new支持自定義刪除器
區(qū)別三:內(nèi)存分配方式不同
由于shared_ptr除了維護(hù)對(duì)象本身的內(nèi)存以外還要維護(hù)一個(gè)控制塊
- ?強(qiáng)引用計(jì)數(shù)?(use_count):記錄有多少個(gè) shared_ptr 共享對(duì)象
- ?弱引用計(jì)數(shù)?(weak_count):記錄有多少個(gè)weak_ptr 觀察對(duì)象?
- 自定義刪除器?(如果存在)。 ?
- 對(duì)象指針?(指向?qū)嶋H分配的對(duì)象)。
make_shared 在底層會(huì) ?一次性分配一塊連續(xù)內(nèi)存,既存儲(chǔ)對(duì)象本身,也存儲(chǔ)控制塊。
但是new shared_ptr會(huì)有兩次分配
std::shared_ptr<MyClass> p(new MyClass);
- 第一次分配:new MyClass 分配對(duì)象內(nèi)存。
- ?第二次分配:shared_ptr 構(gòu)造函數(shù)內(nèi)部為控制塊分配內(nèi)存。
為什么make_unique和直接new unique_ptr都是一次分配內(nèi)存
因?yàn)閡nique_ptr 不需要維護(hù)引用計(jì)數(shù),因此 ?沒(méi)有控制塊。無(wú)論通過(guò) make_unique 還是直接 new,都只需分配對(duì)象內(nèi)存
shared_ptr合并分配的缺點(diǎn)
對(duì)象和控制塊內(nèi)存綁定,即使所有 shared_ptr 銷毀,若仍有 weak_ptr 存在,對(duì)象內(nèi)存仍然需等待控制塊釋放(但析構(gòu)函數(shù)會(huì)被及時(shí)調(diào)用)
shared_ptr控制塊內(nèi)存的延遲釋放是內(nèi)存泄漏嗎?
由于make_shared 是一次性分配一塊連續(xù)內(nèi)存,同時(shí)存儲(chǔ) ?對(duì)象實(shí)例 和 ?控制塊?(包含引用計(jì)數(shù)、弱引用計(jì)數(shù)等)
所以當(dāng)沒(méi)有weak_ptr存在時(shí)
通過(guò)make_shared建立的shared_ptr:
- 對(duì)象析構(gòu)函數(shù)立即被調(diào)用。
- 整塊內(nèi)存(對(duì)象 + 控制塊)立即釋放。
通過(guò)new建立的shared_ptr:
- 對(duì)象內(nèi)存立即釋放。
- 控制塊內(nèi)存也立即釋放。
當(dāng)有weak_ptr存在時(shí)
通過(guò)make_shared建立的shared_ptr:
- 對(duì)象析構(gòu)函數(shù)被調(diào)用(資源清理)。 ?
- 對(duì)象內(nèi)存和控制塊內(nèi)存暫時(shí)保留?(直到所有 weak_ptr 也被銷毀)。
通過(guò)new建立的shared_ptr:
- 對(duì)象內(nèi)存立即釋放(僅保留控制塊內(nèi)存)。
由于make_shared對(duì)象和控制塊內(nèi)存是連續(xù)的,無(wú)法單獨(dú)釋放對(duì)象內(nèi)存。所以必須等待所有 weak_ptr 銷毀后,?整塊內(nèi)存才能一起釋放。
那這是內(nèi)存泄漏嗎?
不是!? 內(nèi)存泄漏的定義是:?無(wú)法再訪問(wèn)且未釋放的內(nèi)存。
而在此時(shí):
對(duì)象析構(gòu)函數(shù)已被調(diào)用(資源已清理)。
內(nèi)存仍被 weak_ptr 的控制塊管理,雖然未釋放,但程序仍能通過(guò) weak_ptr 的機(jī)制感知到內(nèi)存狀態(tài)。
當(dāng)所有 weak_ptr 銷毀后,內(nèi)存會(huì)被正確釋放。
enable_shared_from_this 和 shared_from_this()
解決場(chǎng)景:
當(dāng)一個(gè)對(duì)象已被 shared_ptr 管理時(shí),若在成員函數(shù)中直接用 this 創(chuàng)建新的 shared_ptr,會(huì)導(dǎo)致 多個(gè)獨(dú)立的引用計(jì)數(shù) 。當(dāng)某一 shared_ptr 析構(gòu)時(shí),對(duì)象可能被提前銷毀,引發(fā)懸空指針或雙重釋放。
問(wèn)題場(chǎng)景示例1
class BackgroundWorker { public: void startWork() { // 錯(cuò)誤:裸指針 this 跨線程傳遞,主線程可能提前釋放對(duì)象 std::thread([this]() { doWork(); // 可能訪問(wèn)已銷毀對(duì)象! }).detach(); } void doWork() { /* 自己實(shí)現(xiàn)的操作 */ } }; int main() { auto worker = std::make_shared<BackgroundWorker>(); worker->startWork(); worker.reset(); // 主線程釋放對(duì)象 // 子線程仍在執(zhí)行 doWork(),this 已失效! }
enable_shared_from_this 內(nèi)部保存一個(gè) weak_ptr,指向與對(duì)象關(guān)聯(lián)的 shared_ptr 控制塊。通過(guò) shared_from_this() 方法生成與已有 shared_ptr 共享所有權(quán)的 shared_ptr 確保引用計(jì)數(shù)一致。
解決示例
通過(guò)獲取與 shared_ptr 共享所有權(quán)的智能指針,延長(zhǎng)對(duì)象生命周期
class BackgroundWorker : public std::enable_shared_from_this<BackgroundWorker> { public: void startWork() { // 正確:通過(guò) shared_from_this() 傳遞所有權(quán) std::thread([self = shared_from_this()]() { self->doWork(); // 安全:self 確保對(duì)象存活 }).detach(); } // ... };
問(wèn)題場(chǎng)景示例2:
class TreeNode { public: void addChild() { // 創(chuàng)建一個(gè)子節(jié)點(diǎn) child,用 shared_ptr 管理 auto child = std::make_shared<TreeNode>(); // 錯(cuò)誤:直接用 this 創(chuàng)建新的 shared_ptr,賦值給 child->parent //child->parent引用計(jì)數(shù)也是 1 child->parent = std::shared_ptr<TreeNode>(this); // 危險(xiǎn)! // 將 child 添加到當(dāng)前節(jié)點(diǎn)的 children 列表中 children.push_back(child); } std::shared_ptr<TreeNode> parent; // 父節(jié)點(diǎn)用 shared_ptr 管理 std::vector<std::shared_ptr<TreeNode>> children; // 子節(jié)點(diǎn)列表 }; int main() { // 創(chuàng)建一個(gè)根節(jié)點(diǎn) root,用 shared_ptr 管理 auto root = std::make_shared<TreeNode>();//root引用計(jì)數(shù)是 1 root->addChild(); // 調(diào)用 addChild,導(dǎo)致雙重釋放! }
這段代碼的關(guān)鍵問(wèn)題出現(xiàn)在 child->parent = std::shared_ptr(this)
this 是當(dāng)前 TreeNode 對(duì)象的裸指針(即 root 指向的對(duì)象)。
直接用 this 創(chuàng)建了一個(gè)新的 shared_ptr,賦值給 child->parent。這個(gè)新的 shared_ptr 與 root 是 ?完全獨(dú)立 的,它不知道 root 的存在,因此它的引用計(jì)數(shù) “也是 ?1”。
當(dāng) main 函數(shù)結(jié)束時(shí),root 的引用計(jì)數(shù)減為 ?0,會(huì)釋放它指向的 TreeNode 對(duì)象。
但此時(shí) child->parent 也指向同一個(gè) TreeNode 對(duì)象,且它的引用計(jì)數(shù)仍然是 ?1?(如果 child 未被銷毀)。當(dāng) child 析構(gòu)時(shí),child->parent 的引用計(jì)數(shù)減為 ?0,會(huì)再次嘗試釋放同一個(gè) TreeNode 對(duì)象,導(dǎo)致** ?雙重釋放**。
解決示例:
class TreeNode : public std::enable_shared_from_this<TreeNode> { public: void addChild() { auto child = std::make_shared<TreeNode>(); // 通過(guò) shared_from_this() 獲取與 root 共享所有權(quán)的 shared_ptr child->parent = shared_from_this(); // 引用計(jì)數(shù) +1 children.push_back(child); } std::shared_ptr<TreeNode> parent; std::vector<std::shared_ptr<TreeNode>> children; }; int main() { auto root = std::make_shared<TreeNode>(); root->addChild(); // 安全:root 的引用計(jì)數(shù)變?yōu)?2 }
shared_ptr的線程安全問(wèn)題
http://www.dbjr.com.cn/article/41353.htm
到此這篇關(guān)于C++ unique_ptr、shared_ptr、weak_ptr的區(qū)別小結(jié)的文章就介紹到這了,更多相關(guān)C++ unique_ptr shared_ptr weak_ptr內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用C++ Matlab中的lp2lp函數(shù)教程詳解
本文介紹如何使用C++編寫數(shù)字濾波器設(shè)計(jì)算法,實(shí)現(xiàn)Matlab中的lp2lp函數(shù),將低通濾波器轉(zhuǎn)換為參數(shù)化的低通濾波器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-04-04C語(yǔ)言實(shí)現(xiàn)修改文本文件中特定行的實(shí)現(xiàn)代碼
最近由于項(xiàng)目需要實(shí)現(xiàn)修改文件的功能,所以,博主認(rèn)真查閱了一些資料,但是,很遺憾,并沒(méi)有太多的收獲2013-06-06C++實(shí)現(xiàn)模板中的非類型參數(shù)的方法
這篇文章主要介紹了C++實(shí)現(xiàn)模板中的非類型參數(shù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Cocos2d-x 3.x入門教程(一):基礎(chǔ)概念
這篇文章主要介紹了Cocos2d-x 3.x入門教程(一):基礎(chǔ)概念,本文講解了Director、Scene、Layer、Sprite等內(nèi)容,需要的朋友可以參考下2014-11-11vc6.0中c語(yǔ)言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器)
這篇文章主要介紹了vc6.0中c語(yǔ)言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器),需要的朋友可以參考下2014-04-04C++實(shí)現(xiàn)二叉樹(shù)基本操作詳解
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)二叉樹(shù)基本操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12C語(yǔ)言經(jīng)典例程100例(經(jīng)典c程序100例)
這篇文章主要介紹了C語(yǔ)言經(jīng)典例程100例,經(jīng)典c程序100例,學(xué)習(xí)c語(yǔ)言的朋友可以參考一下2018-03-03使用C++構(gòu)建一個(gè)優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)
優(yōu)先級(jí)隊(duì)列是一種特殊的隊(duì)列數(shù)據(jù)結(jié)構(gòu),本文主要介紹了使用C++構(gòu)建一個(gè)優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02