智慧指標以 RAII 管理動態記憶體,核心機制為參考計數。本文剖析兩種經典實作策略:輔助類 (類 shared_ptr 早期實作) 與控制塊 (現代標準庫實作),附完整代碼與記憶體圖解。
new/delete 易洩漏/雙重釋放/懸空指標。unique_ptr (獨佔)、shared_ptr (共享)、weak_ptr (觀察)。單獨類別 RefPtr 封裝原始指標 + 計數器,智慧指標持有 RefPtr*。
class Point { // 被管理物件
int x, y;
public:
Point(int xv=0, int yv=0):x(xv),y(yv){}
int getX() const { return x; }
int getY() const { return y; }
};
class RefPtr { // 輔助類:私有,僅 SmartPtr 可存取
friend class SmartPtr;
RefPtr(Point* p): ptr(p), count(1) {}
~RefPtr() { delete ptr; }
int count;
Point* ptr;
};
class SmartPtr {
RefPtr* rp;
public:
SmartPtr(Point* p = nullptr) : rp(p ? new RefPtr(p) : nullptr) {}
SmartPtr(const SmartPtr& sp) : rp(sp.rp) { if (rp) ++rp->count; }
SmartPtr& operator=(const SmartPtr& rhs) {
if (rhs.rp) ++rhs.rp->count; // 先增右邊,防自賦值
if (rp && --rp->count == 0) delete rp;
rp = rhs.rp;
return *this;
}
~SmartPtr() { if (rp && --rp->count == 0) delete rp; }
Point* operator->() const { return rp->ptr; }
Point& operator*() const { return *rp->ptr; }
};
SmartPtr (棧上) RefPtr (堆上) Point (堆上)
+----------------+ +----------------+ +----------------+
| rp ───────────+----→ | count: 2 | | x, y |
+----------------+ | ptr ──────────+-----→+----------------+
+----------------+
SmartPtr2 (棧上) ↑
+----------------+ |
| rp ────────────+------+
+----------------+
RefPtr (兩次 new)RefPtr 需為 friend,封裝性弱智慧指標直接持有 T* 與 int* count (控制塊分離),支援多型、自訂刪除器、弱引用。
class Point {
int x, y;
public:
Point(int xv=0, int yv=0):x(xv),y(yv){}
virtual Point* clone() const { return new Point(*this); } // 多型複製
virtual ~Point() = default;
int getX() const { return x; }
int getY() const { return y; }
};
class D3Point : public Point {
int z;
public:
D3Point(int xv, int yv, int zv):Point(xv,yv),z(zv){}
D3Point* clone() const override { return new D3Point(*this); }
int getZ() const { return z; }
};
class SmartPtr {
Point* ptr;
int* count; // 控制塊:單獨配置的計數器
public:
SmartPtr(Point* p = nullptr) : ptr(p), count(p ? new int(1) : nullptr) {}
SmartPtr(Point& obj) : ptr(obj.clone()), count(new int(1)) {} // 多型建構
SmartPtr(const SmartPtr& sp) : ptr(sp.ptr), count(sp.count) {
if (count) ++*count;
}
SmartPtr& operator=(const SmartPtr& sp) {
if (sp.count) ++*sp.count; // 先增
release(); // 再釋放舊資源
ptr = sp.ptr; count = sp.count;
return *this;
}
~SmartPtr() { release(); }
void release() {
if (count && --*count == 0) {
delete ptr;
delete count;
}
}
Point* operator->() const { return ptr; }
Point& operator*() const { return *ptr; }
};
SmartPtr (棧上) 控制塊 (堆上) Point (堆上) +----------------+ +----------+ +----------------+ | ptr ──────────+----→ | count: 2 | | vptr, x, y | | count ────────+----→ +----------+ +----------------+ +----------------+ SmartPtr2 (棧上) ↑ +----------------+ | | ptr ──────────+------+ | count ────────+------+ +----------------+
Point&,透過 virtual clone() 複製實際衍生類別 (D3Point)。weak_ptr 共享控制塊但不增加強引用計數。std::shared_ptr)| 特性 | 教學版控制塊 | std::shared_ptr |
|---|---|---|
| 控制塊內容 | 僅計數器 | 強/弱計數、刪除器、配置器、型別擦除存儲 |
| 配置優化 | 兩次配置 (物件+控制塊) | make_shared 單次配置 (物件+控制塊相鄰) |
| 執行緒安全 | 無 | 原子操作計數器 |
| 自訂刪除器 | 不支援 | 支援 (型別擦除) |
| 陣列支援 | 不支援 | shared_ptr<T[]> (C++17) |
std::make_shared:單次配置、快取友好、例外安全。weak_ptr 打破環。const shared_ptr<T>& (不增計數) 或 T*/T& (觀察者)。shared_ptr 建構後,物件生命週期完全交給它管理。從輔助類到控制塊,智慧指標實作演進反映了 C++ 資源管理的核心哲學:擁有權語意明確化、控制資訊集中化、效能最佳化。理解底層機制能幫助開發者正確選擇 unique/shared/weak、避開循環引用、撰寫高效能現代 C++ 程式碼。