C++14 智慧指標unique_ptr、shared_ptr、weak_ptr
記憶體控制這一大毒瘤,幾乎一直伴隨著C/C++工程師。隨著計算機技術的發展,在boost準標準庫的推動下,C++11終於將unique_ptr、shared_ptr、weak_ptr這幾類智慧指標納入C++中。當然,在這之前還有一種auto_ptr智慧指標,不過由於它的設計存在較大問題(比如:auto_ptr與STL不相容),現階段已經很少能看到它的出現了。
感謝@毛毛是我的小可愛 在評論中指出“ 標題說的還是C++14,文章開頭就跑出C++11。請嚴謹一點!”。
這裡說明一下,(1)上文中提到C++11
出現了智慧指標unique_ptr、shared_ptr、weak_ptr,(2)但是文中的程式碼示例使用了make_unique()
和std::move()
,這些為C++14
中的內容。
智慧指標實質是一個物件,行為表現的卻像一個指標。
shared_ptr和unique_ptr之間的區別在於:shared_ptr是引用計數的智慧指標,而unique_ptr不是。這意味著,可以有多個shared_ptr例項指向同一塊動態分配的記憶體,當最後一個shared_ptr離開作用域時,才會釋放這塊記憶體。shared_ptr也是執行緒安全的。 另一方面,unique_ptr意味著所有權。單個unique_ptr離開作用域時,會立即釋放底層記憶體。
2018-2-28更新 :感謝@奔跑的哇牛 在留言中說到shared_ptr
本身不是執行緒安全的。是的,shared_ptr
本身不是執行緒安全的。陳碩的書中也明確提到了,shared_ptr
的計數功能是原子的,但物件的讀寫不是原子的。c++
標準也只是保證的是weak_ptr
的lock()
指標提升是執行緒安全的。所以,要實現執行緒安全,可能需要weak_ptr
與shared_ptr
配合使用,詳見陳碩的多執行緒書籍。
也可以參考:http://www.pandademo.com/2017/08/thread-safety-of-shared_ptr-and-weak_ptr/
預設的智慧指標應該是unique_ptr。只有需要共享資源時,才使用shared_ptr。
這兩個智慧指標都需要包含<memory>
標頭檔案。
在開始本文之前,首先給出一個類。因為下文中,為了演示智慧指標的使用方式,在較多時候都有用到這個類demo。
#include <memory>
#include <utility> //std::move()
class demo {
public:
demo() : uptr(std::make_unique<int[]>(10)){
printf("demo\n");
for (int i = 0; i < 10; ++i){
uptr[i] = i;
}
}
~demo(){
printf("~demo\n");
}
void show(){
printf("%d\n", uptr[9]);
}
private:
std::unique_ptr<int[]> uptr;
};
unique_ptr
unique_ptr是唯一的,適用於儲存動態分配的舊C風格的陣列。auto關鍵字會自動識別指標型別,當與make_unique配合使用時,即表示unique_ptr智慧指標。
void my_unique_ptr(){
auto uptr = std::make_unique<int[]>(10);
uptr[5] = 17;
printf("%d\n", uptr[5]);
}
這裡應該總是使用auto/make_unique寫法。除非編譯器不支援的情況下,可以這樣寫,
std::unique_ptr<int[]> uptr(new int[10]);
//std::unique_ptr<int[]> uptr = new int[10];//error
注意,上文中,被註釋的那種寫法是不被接受的。再整理一下,unique_ptr可以有以下使用方式,
void my_unique_ptr2(){
auto uptr = std::make_unique<demo>();
uptr->show();
std::unique_ptr<demo> uptr2(new demo());///等效寫法
uptr2->show();
}
另外,需要說明的是,unique_ptr無法使用拷貝建構函式的,上文已經提到過了,這裡再給出一個示例,
void my_unique_ptr3(){
auto uptr = std::make_unique<int>(42);
printf("%d\n", *uptr);//42
///std::unique_ptr<int> uptr1 = uptr; ///unique_ptr無拷貝建構函式
///std::unique_ptr<int> uptr1(uptr); ///等效寫法
std::unique_ptr<int> uptr2 = std::move(uptr);
///printf("%d\n",*uptr); ///error 所有權已交給uptr2
printf("%d\n", *uptr2);
}
既然,無法使用拷貝建構函式,那麼就無法直接使用賦值“=”來轉移指標所有權。但是C++14設計者給開了另外一扇門:std::move。它被包含在<utility>
標頭檔案中。
shared_ptr
shared_ptr用法與unique_ptr類似。如果編譯器支援,你應該總是使用auto/make_shared的寫法,它比直接建立shared_ptr更高效。
void my_shared_ptr(){
///auto sptr = std::make_shared<int[]>(10); //error
///sptr[6] = 20;
auto sptr = std::make_shared<demo>();
sptr->show();
}
上文中已經提到了,智慧指標家族中,unique_ptr是唯一可以適用於舊C風格陣列的指標,shared_ptr等其他智慧指標不能。
shared_ptr除了用於管理純粹的記憶體之外還可以用於其他的目的,比如管理FILE、SOCKET等,極大的增加了程式設計的方便性。
void auto_run_fun(FILE* f){
printf("auto running.\n");
fclose(f);
}
void my_shared_ptr2(){
FILE* f = fopen("data.txt","w");
std::shared_ptr<FILE> file_ptr(f, auto_run_fun);
}
由於shared_ptr是引用計數的,這裡需要極為注意的一點是:糟糕!只呼叫一次建構函式,卻呼叫了兩次解構函式。
正確的使用方式應該是使用make_shared和拷貝建構函式建立副本。範例如下,
void my_shared_ptr3(){
/*demo* d = new demo();
std::shared_ptr<demo> sptr1(d);
std::shared_ptr<demo> sptr2(d);//~demo() error
*/
auto sptr3 = std::make_shared<demo>();
std::shared_ptr<demo> sptr4(sptr3); ///shared_ptr拷貝建構函式
}
shared_ptr引用計數,完全可以返回一個子函式的指標。 在以往的認知中,子函式中的棧空間上的記憶體是無法返回的,而子函式中堆空間上的記憶體是可以返回的(同時,還必須注意手動釋放它,否則必然記憶體洩漏)。
std::shared_ptr<demo> my_shared_ptr4(){
auto sptr = std::make_shared<demo>();
return sptr;
}
那麼,對於以上程式碼的返回值,下文中這樣子使用它,也是非常正確的。
my_shared_ptr4()->show();
智慧指標,完全繼承了JAVA和C#中記憶體託管的風格,而且智慧指標在很多情況下完全可以具體推算出:它會在何時被釋放。
關於shared_ptr更多使用方式,推薦《C++14 N叉樹使用shared_ptr智慧指標》 一文。
weak_ptr
weak_ptr是shared_ptr的黃金夥伴。從上文知道shared_ptr與shared_ptr之間,每拷貝一次,引用計數就會+1,而如果使用weak_ptr則不會出現這個現象。
如果將一個shared_ptr指標賦值給weak_ptr指標,對shared_ptr指標本身不會造成任何影響。對於weak_ptr指標來說,卻可以通過一些方法來探測被賦值過來的shared_ptr指標的有效性,同時weak_ptr指標也可以間接操縱shared_ptr指標。以下主要介紹兩個方法:
- lock() ,weak_ptr指標呼叫lock()方法會獲得一個返回值:shared_ptr。而這個返回值就是被賦值過來的shared_ptr指標,那麼指標都獲得了,當然可以操縱它。
- expired() ,該方法主要用來探測shared_ptr指標的有效性。shared_ptr一旦被釋放,指標就會被置為nullptr。
///weak_ptr -> shared_ptr
void my_weak_ptr(){
std::weak_ptr<demo> wptr;
{
auto sptr = std::make_shared<demo>();
wptr = sptr;
auto sptr2 = wptr.lock();
if (!wptr.expired()){///等價於sptr2 != nullptr
printf("shared_ptr ok\n");
sptr2->show();
}
}
if (wptr.expired()){
printf("shared_ptr deleted\n");
}
}
以上程式碼中,之所以要加個大括號{}在文中,主要是:為了利用變數的作用域原理。 讓shared_ptr指標離開{}作用域後,立即被釋放。
2018-12-27(修改) 北京 海淀
參考文獻:
Marc, Gregoire. C++高階程式設計(第3版)[M]. 北京:清華大學出版社, 2015. 630-636
相關文章
- 智慧指標思想實踐(std::unique_ptr, std::shared_ptr)指標
- C++ 智慧指標詳解: std::unique_ptr 和 std::shared_ptrC++指標
- 智慧指標之手撕共享指標shared_ptr指標
- C++11智慧指標之unique_ptrC++指標
- 智慧指標(auto_ptr 和 shared_ptr)指標
- C++弱引用智慧指標weak_ptr的用處C++指標
- C++基礎回顧4——智慧指標shared_ptrC++指標
- c++動態記憶體智慧指標及weak_ptr用法的理解C++記憶體指標
- C++智慧指標之shared_ptr與右值引用(詳細)C++指標
- c++中關於智慧指標std::tr1::shared_ptr的用法C++指標
- 智慧指標指標
- [CPP] 智慧指標指標
- 什麼是智慧指標?為什麼要用智慧指標?指標
- openfoam 智慧指標探索指標
- vtk智慧指標指標
- 智慧指標學習指標
- 【c++】智慧指標C++指標
- C++智慧指標C++指標
- UE4 智慧指標指標
- 批註:智慧指標分析指標
- C++11 智慧指標C++指標
- 「C++」理解智慧指標C++指標
- auto_ptr 智慧指標指標
- SMART POINTER(智慧指標) (轉)指標
- 智慧指標用法學習指標
- 指標+AI:邁向智慧化,讓指標應用更高效指標AI
- C++ 用智慧指標這樣包裝 this 指標是否可行C++指標
- C++進階(智慧指標)C++指標
- C++11智慧指標用法C++指標
- C++ 智慧指標詳解C++指標
- [CareerCup] 13.8 Smart Pointer 智慧指標指標
- NULL 指標、零指標、野指標Null指標
- C++標準庫有四種智慧指標C++指標
- C++筆記(11) 智慧指標C++筆記指標
- 話說智慧指標發展之路指標
- c++ 智慧指標用法詳解C++指標
- C++智慧指標簡單剖析C++指標
- c++ auto_ptr 智慧指標C++指標