C 如何正確使用智慧型指標?看完這4個點你就明白了

2021-09-29 19:26:39 字數 3917 閱讀 9459

c++11 中推出了三種智慧型指標,unique_ptr、shared_ptr 和 weak_ptr,同時也將 auto_ptr 置為廢棄(deprecated)。

但是在實際的使用過程中,很多人都會有這樣的問題:

不知道三種智慧型指標的具體使用場景

無腦只使用 shared_ptr

認為應該禁用 raw pointer(裸指標,即 widget*這種形式),全部使用智慧型指標

本文試圖理清楚三種智慧型指標的具體使用場景,並講解三種智慧型指標背後的效能消耗。

物件所有權

首先需要理清楚的概念就是物件所有權的概念。所有權在 rust 語言中非常嚴格,寫 rust 的時候必須要清楚自己建立的每個物件的所有權。

但是 c++比較自由,似乎我們不需要明白物件的所有權,寫的**也能正常執行。但是明白了物件所有權,我們才可以正確管理好物件生命週期和記憶體問題。

c++引入了智慧型指標,也是為了更好的描述物件所有權,簡化記憶體管理,從而大大減少我們 c++記憶體管理方面的犯錯機會。

unique_ptr:專屬所有權

我們大多數場景下用到的應該都是 unique_ptr。unique_ptr 代表的是專屬所有權,即由 unique_ptr 管理的記憶體,只能被乙個物件持有。所以,unique_ptr 不支援複製和賦值,如下:

auto w = std::make_unique();

auto w2 = w; // 編譯錯誤

如果想要把 w 複製給 w2, 是不可以的。因為複製從語義上來說,兩個物件將共享同一塊記憶體。

因此,unique_ptr 只支援移動, 即如下:

auto w = std::make_unique();

auto w2 = std::move(w); // w2獲得記憶體所有權,w此時等於nullptr

unique_ptr 代表的是專屬所有權,如果想要把乙個 unique_ptr 的記憶體交給另外乙個 unique_ptr 物件管理。只能使用 std::move 轉移當前物件的所有權。轉移之後,當前物件不再持有此記憶體,新的物件將獲得專屬所有權。

如上**中,將 w 物件的所有權轉移給 w2 後,w 此時等於 nullptr,而 w2 獲得了專屬所有權。

效能

因為 c++的 zero cost abstraction 的特點,unique_ptr 在預設情況下和裸指標的大小是一樣的。所以記憶體上沒有任何的額外消耗,效能是最優的。

使用場景 1:忘記 delete

unique_ptr 乙個最簡單的使用場景是用於類屬性。**如下:

class box

~box()

private:

widget* w;

};如果因為一些原因,w 必須建立在堆上。如果用裸指標管理 w,那麼需要在析構函式中delete w; 這種寫法雖然沒什麼問題,但是容易漏寫 delete 語句,造成記憶體洩漏。

如果按照 unique_ptr 的寫法,不用在析構函式手動 delete 屬性,當物件析構時,屬性w將會自動釋放記憶體。

使用場景 2:異常安全

假如我們在一段**中,需要建立乙個物件,處理一些事情後返回,返回之前將物件銷毀,如下所示:

void process()

在正常流程下,我們會在函式末尾 delete 建立的物件 w,正常呼叫析構函式,釋放記憶體。

但是如果 w->do_something()發生了異常,那麼delete w將不會被執行。此時就會發生記憶體洩漏。我們當然可以使用 try…catch 捕捉異常,在 catch 裡面執行 delete,但是這樣**上並不美觀,也容易漏寫。

如果我們用 std::unique_ptr,那麼這個問題就迎刃而解了。無論**怎麼拋異常,在 unique_ptr 離開函式作用域的時候,記憶體就將會自動釋放。

shared_ptr:共享所有權

在使用 shared_ptr 之前應該考慮,是否真的需要使用 shared_ptr, 而非 unique_ptr。

shared_ptr 代表的是共享所有權,即多個 shared_ptr 可以共享同一塊記憶體。因此,從語義上來看,

shared_ptr 是支援複製的。如下:

auto w = std::make_shared();

cout << w.use_count() << endl; // 1

shared_ptr 內部是利用引用計數來實現記憶體的自動管理,每當複製乙個 shared_ptr,引用計數會+1。當乙個 shared_ptr 離開作用域時,引用計數會-1。當引用計數為 0 的時候,則 delete 記憶體。

同時,shared_ptr 也支援移動。從語義上來看,移動指的是所有權的傳遞。如下:

auto w = std::make_shared();

auto w2 = std::move(w); // 此時w等於nullptr,w2.use_count()等於1

我們將 w 物件 move 給 w2,意味著 w 放棄了對記憶體的所有權和管理,此時 w 物件等於 nullptr。而 w2 獲得了物件所有權,但因為此時 w 已不再持有物件,因此 w2 的引用計數為 1。

效能

記憶體占用高。 shared_ptr 的記憶體占用是裸指標的兩倍。因為除了要管理乙個裸指標外,還要維護乙個引用計數。因此相比於 unique_ptr, shared_ptr 的記憶體占用更高

原子操作效能低。 考慮到執行緒安全問題,引用計數的增減必須是原子操作。而原子操作一般情況下都比非原子操作慢。

使用移動優化效能。shared_ptr 在效能上固然是低於 unique_ptr。而通常情況,我們也可以盡量避免 shared_ptr 複製。如果,乙個 shared_ptr 需要將所有權共享給另外乙個新的 shared_ptr,而我們確定在之後的**中都不再使用這個 shared_ptr,那麼這是乙個非常鮮明的移動語義。對於此種場景,我們盡量使用 std::move,將 shared_ptr 轉移給新的物件。因為移動不用增加引用計數,因此效能比複製更好。

使用場景

shared_ptr 通常使用在共享權不明的場景。有可能多個物件同時管理同乙個記憶體時。

物件的延遲銷毀。陳碩在《linux 多執行緒伺服器端程式設計》中提到,當乙個物件的析構非常耗時,甚至影響到了關鍵執行緒的速度。可以使用blockingqueue將物件轉移到另外乙個執行緒中釋放,從

而解放關鍵執行緒。

shared_from_this

我們往往會需要在類內部使用自身的 shared_ptr,例如:

class widget

}我們需要把當前 shared_ptr 物件同時交由物件 a 進行管理。意味著,當前物件的生命週期的結束不能早於物件 a。因為物件 a 在析構之前還是有可能會使用到a.widget。

如果我們直接a.widget = this;, 那肯定不行, 因為這樣並沒有增加當前 shared_ptr 的引用計數。shared_ptr 還是有可能早於物件 a 釋放。

如果我們使用a.widget = std::make_shared(this);,肯定也不行,因為這個新建立的 shared_ptr,跟當前物件的 shared_ptr 毫無關係。當前物件的 shared_ptr 生命週期結束後,依然會釋放掉當前記憶體,那麼之後a.widget依然是不合法的。

對於這種,需要在物件內部獲取該物件自身的 shared_ptr, 那麼該類必須繼承std::enable_shared_from_this。**如下:

class widget : public std::enable_shared_from_this

}這樣才是合法的做法。

正確使用auto ptr智慧型指標

1,auto ptr類 auto ptr是乙個模板類,定義如下 template class auto ptr 它儲存的是乙個指向type的指標。顧名思義,auto ptr是一種智慧型指標,它包含乙個動態分配記憶體的指標,並在它生命週期結束的時候,銷毀包含的指標所指向的記憶體。例1 void f 這...

指標(4)智慧型指標的使用

why shared ptr 1 如果指標作為類成員時,使用shared ptr封裝原始指標,解決了複製類物件出現的問題 相較原始指標 如果是非記憶體資源 比如 互斥器 可以在構造時再傳乙個刪除器 deleter 引數 shared ptr可以,auto ptr不能 因為shared ptr預設行為...

C 智慧型指標使用

由於 c 語言沒有自動記憶體 機制,程式設計師每次 new 出來的記憶體都要手動 delete。程式設計師忘記 delete,流程太複雜,最終導致沒有 delete,異常導致程式過早退出,沒有執行 delete 的情況並不罕見。std auto ptr boost scoped ptr boost ...