上一篇介紹的代理類雖解決了異構容器問題,但存在兩大痛點:
new 一個新對象,大對象或含外部引用的對象開銷巨大,甚至無法正確實現 copy()。copy(),若在現有代碼庫上擴展往往不被允許。若不複製對象,如何實現多對象綁定同一實體?C++ 中除指針外,引用也能指向同一對象,但作為返回值極其危險(懸垂引用、左值修改、動態內存無法釋放)。最終選擇回歸指針,並通過封裝類消除其安全隱患——這就是句柄,即現代智能指針的雛形。
Point 為例class Point {
public:
Point() : xval(0), yval(0) {}
Point(int x, int y) : xval(x), yval(y) {}
int x() const { return xval; }
int y() const { return yval; }
Point& x(int xv) { xval = xv; return *this; }
Point& y(int yv) { yval = yv; return *this; }
private:
int xval, yval;
};
class Handle {
public:
Handle();
Handle(int, int);
Handle(const Point&);
Handle(const Handle&);
Handle& operator=(const Handle&);
Handle& x(int); // 封裝 Point::x(int)
~Handle();
private:
Point* p;
int* u; // 引用計數指針
};
關鍵決策:引用計數不能放在 Handle 內(無法同步多實例),也不能放在 Point 內(侵入業務類),故額外分配一塊 int 內存,u 指向它。
當多個句柄綁定同一對象時,修改操作該如何表現?
Handle h(3, 4); Handle h2 = h; // 共享同一 Point h2.x(5); // 此時 h.x() 應返回 3 還是 5?
h.x() 返回 5。實現簡單,但修改影響全局,不可預期。h.x() 仍為 3。兼具效率與直觀性,廣泛用於 std::string、OS 頁面管理(fork)等。Handle::Handle() : u(new int(1)), p(new Point) {}
Handle::Handle(int x, int y) : u(new int(1)), p(new Point(x, y)) {}
Handle::Handle(const Point& p0) : u(new int(1)), p(new Point(p0)) {}
Handle::Handle(const Handle& h) : u(h.u), p(h.p) { ++*u; }
Handle& Handle::operator=(const Handle& h) {
++*h.u; // 先增右邊,防自賦值導致過早刪除
if (--*u == 0) { delete u; delete p; }
u = h.u; p = h.p;
return *this;
}
Handle::~Handle() {
if (--*u == 0) { delete u; delete p; }
}
x(int)Handle& Handle::x(int x0) { p->x(x0); return *this; }
x(int)Handle& Handle::x(int x0) {
if (*u != 1) { // 共享時需分離
--*u;
p = new Point(*p); // 深拷貝
u = new int(1); // 新計數器歸 1
}
p->x(x0);
return *this;
}
將計數邏輯封裝為 UseCount,降低耦合:
class UseCount {
public:
UseCount() : p(new int(1)) {}
UseCount(const UseCount& u) : p(u.p) { ++*p; }
~UseCount() { if (--*p == 0) delete p; }
bool only() { return *p == 1; }
bool reattach(const UseCount& u) { // 賦值時重掛計數
++*u.p;
if (--*p == 0) { delete p; p = u.p; return true; }
p = u.p; return false;
}
bool makeonly() { // COW 分離
if (*p == 1) return false;
--*p; p = new int(1); return true;
}
private:
int* p;
};
句柄類精簡為:
class Handle {
public:
Handle() : p(new Point) {}
Handle(int x, int y) : p(new Point(x, y)) {}
Handle(const Point& p0) : p(new Point(p0)) {}
~Handle() { if (u.only()) delete p; }
Handle& operator=(const Handle& h) {
if (u.reattach(h.u)) delete p;
p = h.p; return *this;
}
Handle& x(int x0) {
if (u.makeonly()) p = new Point(*p);
p->x(x0); return *this;
}
private:
Point* p;
UseCount u;
};
| 特性 | 代理類 | 句柄(引用計數) |
|---|---|---|
| 複製策略 | 每次深拷貝 | 共享+COW 可選 |
| 侵入業務類 | 需加 copy() | 零侵入 |
| 內存開銷 | 對象數 = 句柄數 | 對象數 ≤ 句柄數 |
| 語義靈活性 | 固定值語義 | 可選指針/值語義 |
句柄類通過引用計數實現共享所有權,配合 COW 兼顧效率與直觀語義,且不侵入業務代碼,是現代 std::shared_ptr/Copy-on-write 技術的直接源頭。本文出自「菜鳥浮出水」博客:原文鏈接