究竟什么時(shí)候用shared_ptr,什么時(shí)候用unique_ptr?
掃描二維碼
隨時(shí)隨地手機(jī)看文章
最近,有同學(xué)來(lái)問(wèn)我,想了解C++的三種智能指針的使用場(chǎng)景,在項(xiàng)目中應(yīng)該如何選擇?
首先要了解這三種智能指針的特點(diǎn),std::unique_ptr、std::shared_ptr和std::weak_ptr:
std::unique_ptr
std::unique_ptr是一種獨(dú)占所有權(quán)的智能指針,意味著同一時(shí)間內(nèi)只能有一個(gè)unique_ptr指向一個(gè)特定的對(duì)象。
當(dāng)unique_ptr被銷毀時(shí),它所指向的對(duì)象也會(huì)被銷毀。
使用場(chǎng)景:
- 當(dāng)你需要確保一個(gè)對(duì)象只被一個(gè)指針?biāo)鶕碛袝r(shí)。
- 當(dāng)你需要自動(dòng)管理資源,如文件句柄或互斥鎖時(shí)。
- 當(dāng)你不確定用哪種智能指針時(shí),優(yōu)先選擇unique_ptr就沒(méi)毛病。
示例代碼:
#include #include class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; } void test() { std::cout << "Test::test()\n"; } }; int main() { std::unique_ptr ptr(new Test()); ptr->test(); // 當(dāng)ptr離開(kāi)作用域時(shí),它指向的對(duì)象會(huì)被自動(dòng)銷毀 return0; }
std::shared_ptr
std::shared_ptr是一種共享所有權(quán)的智能指針,多個(gè)shared_ptr可以指向同一個(gè)對(duì)象。內(nèi)部使用引用計(jì)數(shù)來(lái)確保只有當(dāng)最后一個(gè)指向?qū)ο蟮?/span>shared_ptr被銷毀時(shí),對(duì)象才會(huì)被銷毀。
使用場(chǎng)景:
- 當(dāng)你需要在多個(gè)所有者之間共享對(duì)象時(shí)。
- 當(dāng)你需要通過(guò)復(fù)制構(gòu)造函數(shù)或賦值操作符來(lái)復(fù)制智能指針時(shí)。
示例代碼:
#include #include class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; }void test() { std::cout << "Test::test()\n"; } }; int main() { std::shared_ptrptr1(new Test()); std::shared_ptrptr2 = ptr1; ptr1->test(); // 當(dāng)ptr1和ptr2離開(kāi)作用域時(shí),它們指向的對(duì)象會(huì)被自動(dòng)銷毀 return0; }
std::weak_ptr
std::weak_ptr是一種不擁有對(duì)象所有權(quán)的智能指針,它指向一個(gè)由std::shared_ptr管理的對(duì)象。weak_ptr用于解決shared_ptr之間的循環(huán)引用問(wèn)題。
是另外一種智能指針,它是對(duì) shared_ptr 的補(bǔ)充,std::weak_ptr 是一種弱引用智能指針,用于觀察 std::shared_ptr 指向的對(duì)象,而不影響引用計(jì)數(shù)。它主要用于解決循環(huán)引用問(wèn)題,從而避免內(nèi)存泄漏,另外如果需要追蹤指向某個(gè)對(duì)象的第一個(gè)指針,則可以使用 weak_ptr。
可以考慮在對(duì)象本身中維護(hù)一個(gè)指向第一個(gè) shared_ptr 的弱引用(std::weak_ptr)。當(dāng)創(chuàng)建對(duì)象的第一個(gè) shared_ptr 時(shí),將這個(gè) shared_ptr 賦值給對(duì)象的 weak_ptr 成員。這樣,在需要時(shí),可以通過(guò)檢查對(duì)象的 weak_ptr 成員來(lái)獲取指向?qū)ο蟮牡谝粋€(gè) shared_ptr(如果仍然存在的話).
使用場(chǎng)景:
- 當(dāng)你需要訪問(wèn)但不擁有由shared_ptr管理的對(duì)象時(shí)。
- 當(dāng)你需要解決shared_ptr之間的循環(huán)引用問(wèn)題時(shí)。
- 注意weak_ptr肯定要和shared_ptr搭配使用。
示例代碼:
#include #include class Test { public: Test() { std::cout << "Test::Test()\n"; } ~Test() { std::cout << "Test::~Test()\n"; }void test() { std::cout << "Test::test()\n"; } }; int main() { std::shared_ptrsharedPtr(new Test()); std::weak_ptrweakPtr = sharedPtr; if (auto lockedSharedPtr = weakPtr.lock()) { lockedSharedPtr->test(); }// 當(dāng)sharedPtr離開(kāi)作用域時(shí),它指向的對(duì)象會(huì)被自動(dòng)銷毀 return0; }
這三種智能指針各有其用途,選擇哪一種取決于你的具體需求。
1)智能指針?lè)矫娴慕ㄗh:
- 盡量使用智能指針,而非裸指針來(lái)管理內(nèi)存,很多時(shí)候利用RAII機(jī)制管理內(nèi)存肯定更靠譜安全的多。
- 如果沒(méi)有多個(gè)所有者共享對(duì)象的需求,建議優(yōu)先使用unique_ptr管理內(nèi)存,它相對(duì)shared_ptr會(huì)更輕量一些。
- 在使用shared_ptr時(shí),一定要注意是否有循環(huán)引用的問(wèn)題,因?yàn)檫@會(huì)導(dǎo)致內(nèi)存泄漏。
- shared_ptr的引用計(jì)數(shù)是安全的,但是里面的對(duì)象不是線程安全的,這點(diǎn)要區(qū)別開(kāi)。
2)為什么std::unique_ptr可以做到不可復(fù)制,只可移動(dòng)?
因?yàn)榘芽截悩?gòu)造函數(shù)和賦值運(yùn)算符標(biāo)記為了delete,見(jiàn)源碼:
template <typename _Tp, typename _Tp_Deleter = default_delete> class unique_ptr { // Disable copy from lvalue. unique_ptr(const unique_ptr&) = delete; template<typename _Up, typename _Up_Deleter> unique_ptr(const unique_ptr<_Up, _Up_Deleter>&) = delete; unique_ptr& operator=(const unique_ptr&) = delete; template<typename _Up, typename _Up_Deleter> unique_ptr& operator=(const unique_ptr<_Up, _Up_Deleter>&) = delete; };
3)shared_ptr的原理:
每個(gè) std::shared_ptr 對(duì)象包含兩個(gè)成員變量:一個(gè)指向被管理對(duì)象的原始指針,一個(gè)指向引用計(jì)數(shù)塊的指針(control block pointer)。
引用計(jì)數(shù)塊是一個(gè)單獨(dú)的內(nèi)存塊,引用計(jì)數(shù)塊允許多個(gè) std::shared_ptr 對(duì)象共享相同的引用計(jì)數(shù),從而實(shí)現(xiàn)共享所有權(quán)。
當(dāng)創(chuàng)建一個(gè)新的 std::shared_ptr 時(shí),引用計(jì)數(shù)初始化為 1,表示對(duì)象當(dāng)前被一個(gè) shared_ptr 管理。
- 拷貝 std::shared_ptr:當(dāng)用一個(gè) shared_ptr 拷貝出另一個(gè) shared_ptr 時(shí),需要拷貝兩個(gè)成員變量(被管理對(duì)象的原始指針和引用計(jì)數(shù)塊的指針),并同時(shí)將引用計(jì)數(shù)值加 1。這樣,多個(gè) shared_ptr 對(duì)象可以共享相同的引用計(jì)數(shù)。
- 析構(gòu) std::shared_ptr:當(dāng) shared_ptr 對(duì)象析構(gòu)時(shí),引用計(jì)數(shù)值減 1。然后檢測(cè)引用計(jì)數(shù)是否為 0。如果引用計(jì)數(shù)為 0,說(shuō)明沒(méi)有其他 shared_ptr 對(duì)象指向該資源,因此需要同時(shí)刪除原始對(duì)象(通過(guò)調(diào)用自定義刪除器,如果有的話)。
4)智能指針的缺點(diǎn)
- 性能開(kāi)銷,需要額外的內(nèi)存來(lái)存儲(chǔ)他們的控制塊,控制塊包括引用計(jì)數(shù),以及運(yùn)行時(shí)的原子操作來(lái)增加或減少引用技術(shù),這可能導(dǎo)致裸指針的性能下降。
- 循環(huán)引用問(wèn)題,如果兩個(gè)對(duì)象通過(guò)成員變量shared_ptr相互引用,并且沒(méi)有其他指針指向這兩個(gè)對(duì)象中的任何一個(gè),那么這兩個(gè)對(duì)象的內(nèi)存將永遠(yuǎn)不會(huì)被釋放,導(dǎo)致內(nèi)存泄露。
#include #include class B;// 前向聲明 class A { public: std::shared_ptr b_ptr; ~A() { std::cout << "A has been destroyed."<< std::endl; } }; class B { public: std::shared_ptr a_ptr; ~B() { std::cout << "B has been destroyed."<< std::endl; } }; int main() { std::shared_ptr a = std::make_shared(); std::shared_ptr b = std::make_shared(); a->b_ptr = b; // A 引用 B b->a_ptr = a; // B 引用 A // 由于存在循環(huán)引用,A 和 B 的析構(gòu)函數(shù)將不會(huì)被調(diào)用,從而導(dǎo)致內(nèi)存泄漏 return0; }
- 智能指針不一定適用于所有場(chǎng)景:有一些容器類,內(nèi)部實(shí)現(xiàn)依賴于裸指針,另外在考慮某些性能關(guān)鍵場(chǎng)景下,使用裸指針可能更合適。但絕大多數(shù)場(chǎng)景,用智能指針就OK。
選型建議
- 默認(rèn)選擇unique_ptr,因?yàn)樗阅茏顑?yōu),且語(yǔ)義清晰,比如局部動(dòng)態(tài)對(duì)象。
- 當(dāng)你發(fā)現(xiàn)unique_ptr使用受限,那大概率就是有需要共享的需求,需要多個(gè)模塊或?qū)ο笮韫蚕硗毁Y源時(shí)(如全局配置、線程間共享數(shù)據(jù)),使用shared_ptr,但要注意循環(huán)引用的問(wèn)題。
- 優(yōu)先使用make_unique和make_shared構(gòu)造對(duì)應(yīng)的智能指針,具備異常安全性。
- 避免裸指針和智能指針混用,容易出現(xiàn)double free等問(wèn)題。
- unique_ptr放心使用,并沒(méi)有額外開(kāi)銷。
- shared_ptr 的引用計(jì)數(shù)可能引發(fā)原子操作開(kāi)銷,除非對(duì)性能有非常極致的要求,否則沒(méi)必要在意這點(diǎn)開(kāi)銷。也要注意循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏。





