| 特性 | 說明 |
| 拷貝/賦值 | 增加強引用計數,線程安全(原子操作) |
| 構造/銷毀 | 構造 +1,銷毀 -1,歸零時調用刪除器釋放對象 |
| 別名構造 | shared_ptr<T>(sp, ptr) 共享控制塊但存儲不同指針(訪問成員子對象) |
| 線程安全 | 控制塊操作原子化;同一 shared_ptr 多線程非 const 訪問需外部同步 |
make_shared | 單次分配對象+控制塊,異常安全、性能更優、緩存友好 |
| 類型轉換 | 專用 static_pointer_cast、dynamic_pointer_cast、const_pointer_cast |
構造與創建最佳實踐
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 條避坑指南(必讀)
- 不要 用同一裸指針構造多個
shared_ptr(雙重釋放)
- 不要 將
this 傳給 shared_ptr(繼承 enable_shared_from_this)
- 不要 在函數實參中創建
shared_ptr(求值順序不定導致洩漏)
- 不要 盲目替換裸指針為
shared_ptr(開銷大、非萬能)
- 警惕 循環引用(配合
weak_ptr 打破)
- 注意 共享對象生命週期較長,平均資源佔用時間增加
- 多線程 下控制塊原子操作開銷大,熱路徑考慮
unique_ptr+移動
- 析構器 執行時間不確定(最後一個
shared_ptr 銷毀時)
- 不要
delete get() 返回的指針
- 不要 用
get() 初始化/重置另一個智能指針
- 記住 最後一個
shared_ptr 銷毀後 get() 指針失效
- 非 new 內存 必須傳遞自定義刪除器
- 類型轉換 必須用
static_pointer_cast 等專用函數
總結
shared_ptr 是共享所有權的標準解決方案,控制塊設計精妙
- 首選
make_shared:單次分配、異常安全、緩存局部性好
- 線程安全僅限控制塊,對象訪問需外部同步
- 循環引用是最大隱患,
weak_ptr 是標準解藥
- 理解
use_count() 僅供調試,不應用於邏輯判斷

GitHub 完整代碼:https://github.com/fengbingchun/Messy_Test
- 上一篇文章: C++11 unique_ptr 完全指南:獨占所有權、移動語義、自定義刪除器與容器應用
- 下一篇文章: Effective C++ 條款 13:以對象管理資源(RAII)
Copyright ©
程式碼之家 All Rights Reserved