Effective C++條款13 C++基本功之智能指針

2021-02-20 ACM算法日常
智能指針簡介

C++語言標準庫STL提供了一套智能指針接口,且一直處於完善狀態,如下圖所示:

最早的智能指針是std::auto_ptr,到c++11才開始廣泛使用,平時用得最多的是這三個:

其中unique_ptr只能指向一個對象,shared_ptr是通用的智能指針,weak_ptr可以理解為只讀用途的指針,這種指針不改變引用計數。

在舊式的C++程序開發過程中,我們會比較習慣用傳統的裸指針來管理資源,通過成對使用new和delete來保證內存安全,這種做法不會造成太大問題,只是在某些情況下會出現內存難於管理的局面。最典型的是存在分支的情況,如下圖所示:

void test()
{
  auto p = new std::string();
  if(...){
    delete p;
    return;
  }
  ...
  delete p;
}

每一處返回,都需要刪除指針p,再來看看智能指針的寫法:

void test()
{
  auto p = std::make_unique<std::string>();
  if(...){
    return;
  }
  ...
}

這樣採用對象的方式來管理內存,通過對象的生命周期來自動釋放內存。

使用unique_ptr和share_ptr

目前智能指針主要使用unique_ptr和share_ptr,兩者的區別如下:

std::shared_ptr 是一種智能指針,它能夠記錄多少個 shared_ptr 指向同一個對象,當引用計數變為零的時候對象會自動執行delete。

std::make_shared 能夠用來消除顯式的使用 new, 並返回這個對象類型的std::shared_ptr指針。例如:

auto pointer = std::make_shared<int>(10);

std::unique_ptr 是一種獨佔的智能指針,它禁止其他智能指針與其共享同一個對象,從而保證代碼的安全,如下所示:

std::unique_ptr<int> pointer = std::make_unique<int>(10); 
std::unique_ptr<int> pointer2 = pointer; // 非法

獨佔也就是不可複製,但是,我們可以利用 std::move 函數將其轉移給其他的 unique_ptr。

弱弱的std::weak_ptr

還有一個智能指針是std::weak_ptr,這個智能指針主要是針對std::shared_ptr的不足而設計的。如下所示:

struct A;
struct B;

struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被銷毀" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被銷毀" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
}

a和b都引用了對方,導致計數都為2,當a和b離開作用域時,引用計數會是1,也就不能釋放內存資源。

解決這個問題的辦法就是使用弱引用指針 std::weak_ptr,std::weak_ptr是一種弱引用(相比較而言 std::shared_ptr 就是一種強引用)。弱引用不會引起引用計數增加,當換用弱引用時候,最終的釋放流程如下圖所示:

std::weak_ptr 沒有 * 運算符和 -> 運算符,所以不能夠對資源進行操作,它的唯一作用就是用於檢查 std::shared_ptr 是否存在,其 expired() 方法能在資源未被釋放時,會返回 true,否則返回 false。

線程安全的智能指針

智能指針的線程安全需要考慮2個問題,一個是多線程狀態下智能指針內部的引用計數是否是線程安全的,另外一個問題是多線程中智能指針指向的值的讀寫是否是線程安全的。

對於前一個問題,C++標準庫聲明shared_ptr是線程安全的,不用擔心引用次數出問題,也不用擔心析構函數會調用多次。

後一個問題其實是對shared_ptr對象的寫入,這個不是線程安全的,可以通過atomic_store和atomic_load來處理安全讀寫。

一般而言,如果一個智能指針在初次賦值後,後續不需要寫入操作,那麼所有的讀取操作都是線程安全的。而如果後續需要改變這個指針的指向,那麼就需要加鎖。

總結

智能指針這種技術並不新奇,在很多語言中都是一種常見的技術,現代 C++ 將這項技術引進,在一定程度上消除了 new/delete 的濫用,是一種更加成熟的編程範式。

相關焦點

  • C++、java 和 C 的區別
    的區分 ,這個跟java和c# 的區別比較大,但c#裡面有unit ulong ushort 這三種就相當於c++的修飾詞unsigned,當c++李明的變量類型定義unsigned,就默認是整數。2.java和c#裡面都有字符串型 和byte型, 但c++裡面沒有,但它是以另外的形式存儲這類型的數據的,比如 java和c#裡面的 byte其實就是unsigned char類型;c++中字符數組就能存儲字符串 (char a[]={"hello"}; ps:注意c++裡面定義數組 變量必須在中括號前面)。
  • c++之重載函數學習總結
    test5.ctest5.c:8:5: error: conflicting types for 『func』 int func(int a, int b)     ^test5.c:3:5: note: previous definition of 『func』 was here int func(int x)     ^test5.c:13
  • c++11新特性,所有知識點都在這了!
    C++線程池的實現之格式修訂版C++定時器的實現之格式修訂版智能指針很多人談到c++,說它特別難,可能有一部分就是因為c++的內存管理吧,不像java那樣有虛擬機動態的管理內存,在程序運行過程中可能就會出現內存洩漏
  • c++ fstream + string 處理大數據
    (4)上面兩點算是自己的誤解吧,因為c++裡面也有也有與之對應的fstream類,c++map容器類,詳見c++ map簡介(5)c++裡面也有相對比較成熟的string類,裡面的函數也大部分很靈活,沒有的也可以很容易的實現split,strim等,詳見c++string實現(6)最近從網上,看到了一句很經典的話,c++的風fstream類 + string
  • c++的輸入與輸出
    c++輸入與輸出C++ 標準庫提供了一組豐富的輸入/輸出功能,本章將討論 C++ 編程中最基本和最常見的 I/O 操作。輸入輸出並不是c++語言的正式組成成分,c和c++沒有為輸入輸出提供專門的結構。在c語言中輸入輸出是通過調用scanf和printf 實現的,在c++中是通過調用流對象cin和cout實現的。
  • c++ 之布爾類型和引用的學習總結!
    --引用相對於指針來說具有更好的可讀性和實用性。5、引用的本質:(1)引用在c++中的內部實現是一個指針常量,比如說說:Type& name; void fun(int& a){  a=8;
  • c++ fstream + string處理大數據
    (4)上面兩點算是自己的誤解吧,因為c++裡面也有也有與之對應的fstream類,c++map容器類,詳見c++map簡介(5)c++裡面也有相對比較成熟的string類,裡面的函數也大部分很靈活,沒有的也可以很容易的實現split,strim等,詳見c++string實現(6)最近從網上,看到了一句很經典的話,c++的風fstream類+string
  • C 2 C++進階篇(1)
    之前一直是對於面向過程的編程,python有過那種對象風格的編程,但是對於oop的實際開發還停留在表面,沒有獨立的開發c++經驗,也有好幾年沒有碰過c了。由於接手Qt的相關項目,所以對c to c++的進階希望能進行個自我總結。
  • 「最佳實踐」C++陷阱與套路
    帶虛函數的類對象會有一個虛函數表的指針,memcpy將破壞該指針指向。## 智能指針理解基於引用計數法的智能指針實現方式,了解所有權轉移的概念,理解shared_ptr和unique_ptr的區別和適用場景。
  • Effective C++ 讀書筆記
    Day 1:Item 1: View c++ as a federation of languages我果然還是喜歡在開頭說一點總結性的東西。這裡對條款1做一點記錄。。一個例子:對內置類型(C-like)而言,pass-by-value通常比pass-by-reference高效;對用戶自定義的對象,由於構造和析構函數的存在,pass-by-reference-to-const往往更好;而在傳遞STL迭代器(iterator)和函數對象(function object)時,pass-by-value則更好,因為它們都是基於C指針實現的。
  • C++ 的門門道道 | 技術頭條 - CSDN
    ,而不是針對對象本身,因為如果對象比較大,交換(複製)對象比交換指針或索引更耗費。帶虛函數的類對象會有一個虛函數表的指針,memcpy將破壞該指針指向。對非POD執行memset/memcpy,免費送你四個字:自求多福。
  • c++之內存分配、命名空間、強制類型轉換學習總結
    一、C++動態內存分配:在學習c語言的時候,我們一般都是使用庫函數malloc()來進行內存的申請分配,然後使用庫函數free()來進行釋放申請到的內存;現在在c++裡面採用了另外一種內存申請的方法:c++中通過
  • C 語言會比 C++ 快?
    但是,這些通常與之前版本的simplifier.cpp非常接近,不同之處在於現在可以直接比較變化。與之相反的是,使用自定義容器,可以輕鬆地選擇跳過初始化。實際上,在我們的替換代碼中這是唯一的選項,因為如果需要,可以很輕鬆的手動添加 memset 設置數組大小。又回到了之前帶有邊界檢查的 operator[] 的自定義容器大部分都是成功的,但它並不能讓我滿意。
  • 跟我學C++中級篇——STL的學習
    一、c++標準庫C++的標準庫主要包含兩大類,首先是包含C的標準庫的,當然,為了適應c++對一些C庫進行了少許的修改和增加。最重要的當然是面向對象的c++庫;而c++庫又可以分成兩大類,即面向對象的c++庫和標準模板庫,也就是題目中的STL。
  • C++ 優先隊列priority_queue
    只有排好了隊伍才會有落後和優先之分,否則一團亂糟糟的,怎麼才能分出優先的,所以優先隊列一定應用了排序。可是排序要怎樣實現呢?其實排序這個底層邏輯你是不用管的,你只要把想要的數據放到優先隊列裡,然後取出的必定是當前狀態下最優的,當然,究竟什麼是最優的條件是需要你來設定的,也就是說我們需要定義排序的規則。
  • C++ | 虛函數簡介
    本文將簡單探究一下 c++ 中的虛函數實現機制。本文主要基於 vs2013 生成的 32 位代碼進行研究,相信其它編譯器(比如,gcc)的實現大同小異。先從對象大小開始 假設我們有如下代碼,假設 int 佔 4 字節,指針佔 4 字節。
  • C++卡牌小遊戲
    以前上學的時候業餘學了點c++ ,也僅僅用來做過一個控制臺版的「學生管理系統」,現在工作接觸最多的還是C語言,c++那各種屌炸天的語法和
  • C++之字符串類學習總結
    一、回顧c語言對字符串的實現:一般我們在c語言要實現對字符串操作的話,一般是採用字符數組或者一組函數來實現的,為啥這樣做呢,那是因為c語言裡面根本就沒有字符串類型的關鍵字;而且c語言也支持自定義類型,所以更加無法獲得字符串類型為了解決這個問題,在c++
  • 九大程式語言優缺點第四期:c++
    C++:c++誕生於1983年,緊隨c語言的步伐,c++是C語言的超集,大家所知道的C語言是面向過程的,java是面向對象的,那麼C語言為了面向對象,所以誕生出現在大家所熟知的c++,被廣泛視為大規模應用構建軟體。
  • python+C、C++混合編程的應用
    有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。在有些領域,比如通信,性能很關鍵,但並不意味這個領域的coder只能苦苦掙扎於c/c++的陷阱中,比如可以使用多種語言混合編程。