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





