首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++11 shared_ptr 完全指南:共享所有權、控制塊、make_shared、線程安全與避坑指南

     編輯:關於C++

std::shared_ptr 是 C++11 實現共享所有權的智能指針,通過引用計數機制自動管理動態對象生命週期。多個 shared_ptr 可指向同一對象,最後一個銷毀時自動釋放資源。本文系統講解核心機制、關鍵 API、線程安全、常見陷阱及最佳實踐。

核心架構:控制塊

每個 shared_ptr 實例包含兩個指針:

  • 存儲指針:指向對象(get()、解引用、比較操作使用)
  • 控制塊指針:指向控制塊,包含:
      • 強引用計數(use_count())
      • 弱引用計數(weak_ptr 數量)
      • 刪除器(可自定義)
      • 分配器(可自定義)
      • 可選:對象本身(make_shared 分配在同一塊內存)

關鍵特性

構造與創建最佳實踐

1. 優先使用 make_shared

auto p1 = std::make_shared<std::string>("hello"); // 高效、異常安全
auto p2 = std::make_shared<MyClass>(arg1, arg2);  // 完美轉發構造參數

// 等價於(但分兩次分配、無異常安全保證)
std::shared_ptr<MyClass> p3(new MyClass(arg1, arg2));

2. 自定義刪除器

// Lambda 刪除器
auto p = std::shared_ptr<int>(new int(42), [](int* p){ delete p; });

// 結構體刪除器(需指定類型)
struct Deleter { void operator()(int* p) { delete p; } };
std::shared_ptr<int, Deleter> p2(new int(42), Deleter());

3. 別名構造:訪問子對象共享生命週期

struct Foo { int x; };
auto sp = std::make_shared<Foo>();
std::shared_ptr<int> sp_x(sp, &sp->x); // sp_x 共享 sp 的控制塊,存儲 &sp->x
// sp_x.use_count() == 2, sp_x.get() == &sp->x

實戰代碼示例

1. 多線程共享所有權

struct Base { Base() { std::cout << "Base::Base\n"; } ~Base() { std::cout << "Base::~Base\n"; } };
struct Derived : Base { Derived() { std::cout << "Derived::Derived\n"; } ~Derived() { std::cout << "Derived::~Derived\n"; } };

void thr(std::shared_ptr<Base> p) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::shared_ptr<Base> lp = p; // 拷貝,原子增加計數
    std::cout << "thread: lp.use_count() = " << lp.use_count() << '\n';
}

int test_shared_ptr1() {
    auto p = std::make_shared<Derived>();
    std::cout << "main: p.use_count() = " << p.use_count() << '\n'; // 1
    
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);
    p.reset(); // 主線程釋放,計數 3 → 0? 不,線程持有拷貝,計數 3
    std::cout << "after reset: p.use_count() = " << p.use_count() << '\n'; // 0
    
    t1.join(); t2.join(); t3.join(); // 最後一個線程銷毀時釋放對象
    std::cout << "All threads completed, last one deleted Derived\n";
    return 0;
}

2. 各種構造方式與 use_count

int test_shared_ptr2() {
    struct C { int* data; };
    
    std::shared_ptr<int> p1;                    // 空
    std::shared_ptr<int> p2(nullptr);           // 空
    std::shared_ptr<int> p3(new int);           // 1
    std::shared_ptr<int> p4(new int, std::default_delete<int>()); // 1
    std::shared_ptr<int> p5(new int, [](int* p){delete p;}, std::allocator<int>()); // 1
    std::shared_ptr<int> p6(p5);                // 2
    std::shared_ptr<int> p7(std::move(p6));     // 2 (p6 置空)
    std::shared_ptr<int> p8(std::unique_ptr<int>(new int)); // 1
    
    std::shared_ptr<C> obj(new C);
    std::shared_ptr<int> p9(obj, obj->data);   // 別名構造,計數 2
    
    // 輸出 use_count
    return 0;
}

3. 引用計數動態演示

int test_shared_ptr3() {
    auto a = std::make_shared<int>(10); // a=10, count=1
    auto b = std::make_shared<int>(20); // b=20, count=1
    auto c = a;                          // a=10, count=2
    auto d = std::make_shared<int>(30); // d=30, count=1
    auto e = b;                          // b=20, count=2
    a = d;                               // a=30(count=2), 10(count=1→0 釋放)
    b = std::make_shared<int>(40);       // b=40(count=1), 20(count=1→0 釋放)
    auto f = c;                          // c=10? 已釋放!c 是懸空!實際 c 指向已釋放內存
    b = f;                               // b=10? 未定義行為
    
    // 正確示例:
    auto a = std::make_shared<int>(10);
    auto b = std::make_shared<int>(20);
    auto c = a; // count=2
    auto d = std::make_shared<int>(30);
    auto e = b; // count=2
    a = d;      // a->30(count=2), 10(count=1)
    b = std::make_shared<int>(40); // b->40(1), 20(count=1)
    auto f = c; // f->10(count=2)
    b = f;      // b->10(count=3), 40(count=0 釋放)
    // 最終:10(count=3), 30(count=2), 20(count=1)
    return 0;
}

4. 自定義刪除器統計

template <typename T>
struct Deleter {
    void operator()(T* ptr) { ++count; delete ptr; }
    static int count;
};
template <typename T> int Deleter<T>::count = 0;

int test_shared_ptr5() {
    {
        std::shared_ptr<int> p1(new int(1998), Deleter<int>());
        std::shared_ptr<int> p2(new int(2011), Deleter<int>());
        std::shared_ptr<double> p3(new double(3.17), Deleter<double>());
        std::shared_ptr<MyInt> p4(new MyInt(2017), Deleter<MyInt>());
    } // 作用域結束,全部銷毀
    std::cout << "Deleted " << Deleter<int>::count << " int values.\n";
    std::cout << "Deleted " << Deleter<double>::count << " double values.\n";
    std::cout << "Deleted " << Deleter<MyInt>::count << " MyInt values.\n";
    return 0;
}

13 條避坑指南(必讀)

  1. 不要 用同一裸指針構造多個 shared_ptr(雙重釋放)
  2. 不要 將 this 傳給 shared_ptr(繼承 enable_shared_from_this)
  3. 不要 在函數實參中創建 shared_ptr(求值順序不定導致洩漏)
  4. 不要 盲目替換裸指針為 shared_ptr(開銷大、非萬能)
  5. 警惕 循環引用(配合 weak_ptr 打破)
  6. 注意 共享對象生命週期較長,平均資源佔用時間增加
  7. 多線程 下控制塊原子操作開銷大,熱路徑考慮 unique_ptr+移動
  8. 析構器 執行時間不確定(最後一個 shared_ptr 銷毀時)
  9. 不要 delete get() 返回的指針
  10. 不要 用 get() 初始化/重置另一個智能指針
  11. 記住 最後一個 shared_ptr 銷毀後 get() 指針失效
  12. 非 new 內存 必須傳遞自定義刪除器
  13. 類型轉換 必須用 static_pointer_cast 等專用函數

總結

  • shared_ptr 是共享所有權的標準解決方案,控制塊設計精妙
  • 首選 make_shared:單次分配、異常安全、緩存局部性好
  • 線程安全僅限控制塊,對象訪問需外部同步
  • 循環引用是最大隱患,weak_ptr 是標準解藥
  • 理解 use_count() 僅供調試,不應用於邏輯判斷

shared_ptr 支持的操作表

GitHub 完整代碼:https://github.com/fengbingchun/Messy_Test

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性說明
拷貝/賦值增加強引用計數,線程安全(原子操作)
構造/銷毀構造 +1,銷毀 -1,歸零時調用刪除器釋放對象
別名構造shared_ptr<T>(sp, ptr) 共享控制塊但存儲不同指針(訪問成員子對象)
線程安全控制塊操作原子化;同一 shared_ptr 多線程非 const 訪問需外部同步
make_shared單次分配對象+控制塊,異常安全、性能更優、緩存友好
類型轉換專用 static_pointer_cast、dynamic_pointer_cast、const_pointer_cast