C++11 引入的三大智慧指標——unique_ptr、shared_ptr、weak_ptr——徹底改變了 C++ 資源管理模式,實現了 RAII(資源獲取即初始化)的現代化標準。本文深度解析三者核心機制、所有權語義、效能特徵、執行緒安全、循環引用破解、自定義刪除器、陣列支援,並提供生產級最佳實踐模式與避坑指南。
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有權 | 獨佔(單一擁有者) | 共享(引用計數) | 觀察(不佔有所有權) |
| 拷貝語義 | 禁用(刪除拷貝構造/賦值) | 允許(增加引用計數) | 允許(不影響引用計數) |
| 移動語義 | 支援(轉移所有權) | 支援(轉移所有權,計數不變) | 支援 |
| 記憶體開銷 | 零開銷(單指標大小) | 控制塊:2指標+計數器(通常 24-32 bytes) | 同 shared_ptr(共享控制塊) |
| 釋放時機 | 離開作用域/移動後 | 引用計數歸零 | 不直接釋放,配合 shared_ptr |
| 典型用途 | 獨佔資源、工廠返回、Pimpl | 多方共享、緩存、觀察者模式 | 打破循環引用、緩存觀察、避免懸空 |
#include <memory>
#include <iostream>
using namespace std;
int main() {
// 構造
unique_ptr<int> up1(new int(42));
unique_ptr<int> up2 = make_unique<int>(100); // C++14 推薦
// 移動語義(所有權轉移)
unique_ptr<int> up3 = move(up1); // up1 變空,up3 擁有對象
// unique_ptr<int> up4 = up1; // 編譯錯誤:拷貝被刪除
// 釋放所有權
up3.reset(); // 刪除對象,up3 變空
up3.reset(new int); // 重新獲取新對象
// 釋放但不刪除(極少用)
int* raw = up3.release(); // up3 變空,需手動 delete raw
// 存取
cout << *up2 << endl; // 解引用
cout << up2.get() << endl; // 獲取原始指標
return 0;
}
// 工廠函數:返回 unique_ptr 轉移所有權
unique_ptr<Widget> createWidget() {
return make_unique<Widget>(); // RVO/移動優化,零拷貝
}
// 接收 unique_ptr:按值傳遞(隱含移動)
void consumeWidget(unique_ptr<Widget> w) { // 所有權轉移給函數
// 使用 w...
} // 函數結束自動釋放
// 需保留所有權:傳 const 引用或 raw pointer
void observeWidget(const unique_ptr<Widget>& w) { /* 不獲取所有權 */ }
void observeWidget(Widget* w) { /* 非擁有觀察 */ }
unique_ptr<T[]>// 自動呼叫 delete[]
unique_ptr<int[]> arr(new int[100]);
arr[0] = 42; // 支援下標運算符
// 自定義刪除器管理陣列
unique_ptr<int, void(*)(int*)> arr2(
new int[100],
[](int* p) { delete[] p; } // 或使用 default_delete<int[]>
);
// 檔案句柄 RAII
struct FileDeleter {
void operator()(FILE* f) const { if (f) fclose(f); }
};
unique_ptr<FILE, FileDeleter> fp(fopen("data.txt", "r"));
// Lambda 刪除器(需捕獲時)
auto deleter = [](Socket* s) { s->close(); delete s; };
unique_ptr<Socket, decltype(deleter)> sock(new Socket(), deleter);
shared_ptr 內部持有兩個指標:
#include <memory>
#include <iostream>
using namespace std;
void func(shared_ptr<int> sp) {
cout << "func: use_count = " << sp.use_count() << endl; // +1
}
int main() {
shared_ptr<int> sp1 = make_shared<int>(42); // 計數=1
cout << "sp1: " << sp1.use_count() << endl; // 1
{
shared_ptr<int> sp2 = sp1; // 拷貝,計數=2
cout << "sp2: " << sp2.use_count() << endl; // 2
func(sp1); // 傳值,計數=3(進入),返回後-1
} // sp2 銷毀,計數=1
cout << "after block: " << sp1.use_count() << endl; // 1
sp1.reset(); // 計數=0,刪除對象
return 0;
}
// 兩次分配:new T + new ControlBlock
shared_ptr<Widget> sp1(new Widget());
// 一次分配:物件 + 控制塊相鄰內存(效能更好、區域性更佳)
shared_ptr<Widget> sp2 = make_shared<Widget>();
// 注意:make_shared 無法指定自定義刪除器
// 需自定義刪除器時用 new + shared_ptr 建構子
shared_ptr<T[]>)// C++17 直接支援
shared_ptr<int[]> arr = make_shared<int[]>(100); // 自動 delete[]
// C++11/14 需自定義刪除器
shared_ptr<int> arrLegacy(new int[100], [](int* p){ delete[] p; });
// 或使用 default_delete<int[]>
shared_ptr<int> arrLegacy2(new int[100], default_delete<int[]>());
struct Big { int data[1000]; int id; };
shared_ptr<Big> big = make_shared<Big>();
big->id = 42;
// 共享 big 的生命週期,但指向其成員
shared_ptr<int> idPtr(big, &big->id); // 別名構造
// big 釋放時 idPtr 也失效,避免懸空指標
struct Parent;
struct Child;
struct Parent {
shared_ptr<Child> child;
~Parent() { cout << "Parent destroyed\n"; }
};
struct Child {
shared_ptr<Parent> parent; // 循環!
~Child() { cout << "Child destroyed\n"; }
};
void cycleLeak() {
auto parent = make_shared<Parent>();
auto child = make_shared<Child>();
parent->child = child;
child->parent = parent; // 計數永遠 ≥ 2,永不釋放!
} // 記憶體洩漏
struct Child {
weak_ptr<Parent> parent; // 弱引用,不增加計數
~Child() { cout << "Child destroyed\n"; }
};
void cycleFixed() {
auto parent = make_shared<Parent>();
auto child = make_shared<Child>();
parent->child = child;
child->parent = parent; // parent 計數=1, child 計數=1
} // 正常釋放
shared_ptr<int> sp = make_shared<int>(42);
weak_ptr<int> wp = sp; // 綁定,不增加強計數
cout << "wp.use_count(): " << wp.use_count() << endl; // 1 (強計數)
cout << "wp.expired(): " << wp.expired() << endl; // false
// 安全獲取 shared_ptr:lock() 返回 shared_ptr 或 nullptr
if (auto locked = wp.lock()) {
cout << "Value: " << *locked << endl; // 安全存取
}
sp.reset(); // 強計數歸零,對象被刪除
cout << "wp.expired(): " << wp.expired() << endl; // true
cout << "wp.lock(): " << (wp.lock() ? "valid" : "null") << endl; // null
| 場景 | shared_ptr | weak_ptr | 說明 |
|---|---|---|---|
| 多執行緒**讀**同一 shared_ptr | ✅ 安全 | ✅ 安全 | 引用計數原子操作 |
| 多執行緒**寫**同一 shared_ptr | ❌ 不安全 | ❌ 不安全 | 需外部鎖保護 |
| 多執行緒操作**不同** shared_ptr(共享對象) | ✅ 安全 | ✅ 安全 | 各自修改各自的控制塊指標,計數原子增減 |
多執行緒 lock() weak_ptr | - | ✅ 安全 | 原子檢查計數並構造 shared_ptr |
// 線程間傳遞:複製 shared_ptr(增加計數),各自擁有副本
void threadFunc(shared_ptr<Data> data) { // 按值傳遞,所有權共享
// 使用 data...
}
shared_ptr<Data> globalData = make_shared<Data>();
thread t1(threadFunc, globalData); // 複製,計數+1
thread t2(threadFunc, globalData); // 複製,計數+1
// 主線程 globalData 仍持有,計數≥1
t1.join(); t2.join();
| 操作 | unique_ptr | shared_ptr | 備註 |
|---|---|---|---|
| 構造 | ~1 ns | ~5-10 ns | make_shared 更快 |
| 拷貝/移動 | 移動 ~1 ns | 拷貝 ~3 ns (原子增) | unique_ptr 禁用拷貝 |
| 解引用 | ~0 ns (內聯) | ~0 ns (內聯) | 零開銷 |
| 銷毀 | ~1 ns | ~5 ns (原子減+檢查) | 計數歸零時刪除對象 |
| 記憶體 | 1 指標 (8/16 bytes) | 2 指標 + 控制塊 (24-48 bytes) | 控制塊獨立分配 |
| 陷阱 | 現象 | 解決方案 |
|---|---|---|
| 雙重刪除 | 兩個 shared_ptr 從同一 raw pointer 構造 | 嚴禁 shared_ptr<T> p1(ptr), p2(ptr);只用 make_shared 或單一構造後拷貝 |
| this 指標陷阱 | 類內部 shared_ptr<This>(this) 導致多控制塊 | 繼承 enable_shared_from_this<T>,用 shared_from_this() |
| 循環引用洩漏 | Parent-Child 雙向 shared_ptr | 一方改用 weak_ptr |
| 陣列 delete 錯誤 | shared_ptr<T> p(new T[10]) 呼叫 delete 而非 delete[] | C++17 用 shared_ptr<T[]>;舊標準用自定義刪除器 |
| 移動後使用 | up2 = move(up1); 後使用 up1 | up1 變空,if (up1) 檢查或設計避免移後用 |
| 控制塊分離 | shared_ptr<Base>(new Derived) 刪除時不調用 Derived 析構 | 確保構造時類型完整,或用 make_shared<Derived>() 隱式轉換 |
class Widget : public enable_shared_from_this<Widget> {
public:
shared_ptr<Widget> getShared() { return shared_from_this(); }
// 禁止裸指標構造,強制用工廠
static shared_ptr<Widget> create() { return make_shared<Widget>(); }
private:
Widget() = default;
};
// 使用
auto w = Widget::create();
auto w2 = w->getShared(); // 安全,共享同一控制塊
shared_ptr<T[]> 原生陣列支援、std::make_shared 支援陣列、std::weak_ptr::lock() noexceptstd::atomic<shared_ptr<T>> 原子共享指標、std::shared_ptr 支援 std::formatterstd::out_ptr/std::inout_ptr 與 C API 互操作、std::unique_ptr 構造函數推導指引 (CTAD) 完善智慧指標是現代 C++ 資源管理的基石:
make_unique (C++14)make_shared 優化分配lock() 安全提升,**破解循環引用關鍵**核心原則:
unique_ptr,需要共享才升級 shared_ptrunique_ptr 或 shared_ptr,避免裸 newweak_ptr掌握三大智慧指標的機制邊界與組合模式,能讓你編寫無記憶體洩漏、無懸空指標、執行緒安全、效能最優的現代 C++ 代碼。建議配合 std::span (C++20)、std::expected (C++23) 等新設施,構建完整的資源安全工程體系。