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

C++ 參考計數機制深度解析:從 More Effective C++ 到工程實踐

     編輯:關於C++

引言:為什麼需要參考計數?

在 C++ 中,物件的複製、建構、解構往往伴隨著昂貴的記憶體分配與資料拷貝。參考計數技術讓多個智慧指標共享同一個物件實體,僅在最後一個引用釋放時才真正刪除物件,極大降低了複製成本。這是 Java 等語言自動記憶體管理的核心思想之一,在 C++ 中透過智慧指標實現。

一、經典實現:RCObject + RCPtr

Scott Meyers 在《More Effective C++》中給出的基礎框架:

1.1 基礎類別 RCObject

class RCObject {
public:
    RCObject() : refCount(0), shareable(true) {}
    RCObject(const RCObject&) : refCount(0), shareable(true) {}
    RCObject& operator=(const RCObject&) { return *this; }
    virtual ~RCObject() = 0;

    void addReference() { ++refCount; }
    void removeReference() { if (--refCount == 0) delete this; }
    void markUnshareable() { shareable = false; }
    bool isShareable() const { return shareable; }
    bool isShared() const { return refCount > 1; }

private:
    int refCount;
    bool shareable;
};
RCObject::~RCObject() {}

1.2 智慧指標 RCPtr

template <class T>
class RCPtr {
public:
    RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }
    RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }
    ~RCPtr() { if (pointee) pointee->removeReference(); }

    RCPtr& operator=(const RCPtr& rhs) {
        if (pointee != rhs.pointee) {
            if (pointee) pointee->removeReference();
            pointee = rhs.pointee;
            init();
        }
        return *this;
    }
    T* operator->() const { return pointee; }
    T& operator*() const { return *pointee; }

private:
    T* pointee;
    void init() {
        if (!pointee) return;
        if (!pointee->isShareable()) pointee = new T(*pointee);
        pointee->addReference();
    }
};

1.3 String 類別應用範例

利用 RCPtr<StringValue> 實現 Copy-on-Write (COW) 字串:

class String {
public:
    String(const char* value = "") : value(new StringValue(value)) {}
    const char& operator[](int index) const { return value->data[index]; }
    char& operator[](int index) {
        if (value->isShared()) value = new StringValue(value->data);
        value->markUnshareable();
        return value->data[index];
    }
private:
    struct StringValue : public RCObject {
        char* data;
        StringValue(const char* initValue) { init(initValue); }
        StringValue(const StringValue& rhs) { init(rhs.data); }
        void init(const char* v) { data = new char[strlen(v)+1]; strcpy(data, v); }
        ~StringValue() { delete [] data; }
    };
    RCPtr<StringValue> value;
};

讀取操作直接回傳引用;寫入時若發現共享(isShared()),則觸發深拷貝並標記不可共享,確保修改不影響其他引用者。

二、進階模式:RCIPtr —— 適用於無法修改的既有類別

當被管理物件(如 Widget)無法繼承 RCObject 時,可引入中介層 CountHolder:

template <class T>
class RCIPtr {
    struct CountHolder : public RCObject {
        ~CountHolder() { delete pointee; }
        T* pointee;
    };
    CountHolder* counter;
    // ... 類似 RCPtr,但透過 counter 間接管理 pointee
    void makeCopy() {
        if (counter->isShared()) {
            T* old = counter->pointee;
            counter->removeReference();
            counter = new CountHolder;
            counter->pointee = new T(*old);
            counter->addReference();
        }
    }
};

此模式將參考計數邏輯與業務物件完全解耦,適用於第三方庫或無法修改的舊代碼。

三、效能評估與適用場景

適用條件說明
大量物件共享少量實值如字串池、資源快取、飛行權模式
物件建構/銷毀成本高大型資料結構、資料庫連線、GPU 資源
記憶體佔用敏感嵌入式系統、大規模並發服務

⚠️ 關鍵限制與風險

  • 多線程同步開銷:refCount 的增減必須是原子操作,鎖或原子指令的成本可能抵消共享收益。
  • 循環引用洩漏:A 引用 B、B 引用 A 導致計數永不歸零,需引入弱引用機制打破循環。
  • 效能陷阱:對於小型、短生命週期物件,參考計數的額外開銷(控制塊、原子操作)可能比直接複製更慢。

四、現代 C++ 最佳實踐

C++11 標準庫已提供成熟的實現:

  • std::shared_ptr:標準參考計數智慧指標,執行緒安全的控制塊。
  • std::weak_ptr:打破循環引用的觀察者。
  • std::make_shared:單次分配物件與控制塊,提升效率並減少記憶體碎片。

除非有極特殊需求(如自定義刪除器、異構控制塊),生產代碼應優先使用標準庫組件,避免重複造輪子帶來的維護負擔。

結語

參考計數是資源管理的基石技術,理解其內部機制有助於正確選擇與使用智慧指標。但在多線程高併發場景下,務必權衡同步開銷與共享收益;對於簡單類別,移動語義與 std::unique_ptr 往往是更輕量、更安全的選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved