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

C++ 寫時複製 (COW) 字串類實作:參考計數與效能優化

     編輯:關於C++

動機:避免不必要的深拷貝

傳統 String 拷貝建構/指派皆 分配新記憶體、複製資料。若僅讀取 (如傳參、回傳、存入容器),這造成大量無效配置與複製開銷。Copy-On-Write (COW) 讓多個物件共享同一緩衝區,只有在修改時才複製。

核心設計:參考計數

  • 每個緩衝區附帶引用計數器 (int* refCount)。
  • 建構/拷貝/指派時 ++*refCount。
  • 解構/指派覆蓋前 --*refCount,歸零時 delete[] buffer; delete refCount;。
  • 修改操作 (如 operator[] 非 const 版、append) 檢查 *refCount > 1 → 分離:配置新緩衝、複製資料、遞減舊計數、綁定新緩衝。

實作一:分離式計數器 (教學版)

class String {
    char* data;
    int*  refCount;
public:
    String(const char* s = "") : data(new char[strlen(s)+1]), refCount(new int(1)) {
        strcpy(data, s);
    }
    String(const String& rhs) : data(rhs.data), refCount(rhs.refCount) {
        ++*refCount;
    }
    ~String() { release(); }

    String& operator=(const String& rhs) {
        if (this != &rhs) {
            release();
            data = rhs.data;
            refCount = rhs.refCount;
            ++*refCount;
        }
        return *this;
    }

    // 讀取:直接共享
    char operator[](size_t i) const { return data[i]; }

    // 寫入:COW 分離
    char& operator[](size_t i) {
        if (*refCount > 1) detach();
        return data[i];
    }

private:
    void detach() {
        --*refCount;
        data = new char[strlen(data)+1];
        strcpy(data, data);  // 注意:這裡應為 strcpy(data, 原data),略
        refCount = new int(1);
    }
    void release() {
        if (--*refCount == 0) { delete[] data; delete refCount; }
    }
};

COW 參考計數示意:多物件共享緩衝區

實作二:緩衝區前置計數器 (空間優化)

將 refCount 放在緩衝區前 4 位元組,一次 new 完成,減少配置開銷、提升快取局部性。

class String {
    char* data;  // 指向實際字元資料 (已偏移 4 bytes)
public:
    String(const char* s = "") {
        size_t len = strlen(s);
        char* buf = new char[len + 1 + 4];  // 4 bytes for refCount
        *reinterpret_cast(buf) = 1;   // refCount = 1
        data = buf + 4;
        strcpy(data, s);
    }
    String(const String& rhs) : data(rhs.data) { ++ref(); }

    ~String() { if (--ref() == 0) delete[] (data - 4); }

    String& operator=(const String& rhs) {
        if (this != &rhs) {
            if (--ref() == 0) delete[] (data - 4);
            data = rhs.data;
            ++ref();
        }
        return *this;
    }

    char operator[](size_t i) const { return data[i]; }
    char& operator[](size_t i) {
        if (ref() > 1) detach();
        return data[i];
    }

private:
    int& ref() { return *reinterpret_cast(data - 4); }
    void detach() {
        --ref();
        size_t len = strlen(data);
        char* buf = new char[len + 1 + 4];
        *reinterpret_cast(buf) = 1;
        data = buf + 4;
        strcpy(data, data - 4 + 4);  // 複製原字串
    }
};

前置計數器佈局:refCount + 字元資料連續記憶體

COW 在多執行緒環境的挑戰

  • 參考計數競爭:++/-- 需原子操作 (std::atomic)。
  • 分離檢查與執行非原子:if (ref>1) detach() 之間可能有其他執行緒修改計數 → 需鎖或無鎖演算法。
  • 現代 C++ 標準庫 std::string (C++11 起) 棄用 COW,改用小字串優化 (SSO) + 移動語意,避免多執行緒複雜度與 COW 的鎖開銷。

測試驗證

void testCOW() {
    String s1("hello");
    String s2 = s1;           // 共享緩衝,refCount=2
    String s3; s3 = s1;       // 共享緩衝,refCount=3

    cout << s1[0];            // 讀取,無分離
    s2[0] = 'H';              // 寫入,觸發分離 s2 獨享新緩衝
    // s1/s3 仍共享原緩衝,s2 獨立
}

總結與現代替代方案

技術優點缺點適用場景
COW讀取零拷貝、單執行緒高效多執行緒需鎖、寫入延遲、實作複雜舊標準庫、單執行緒高頻讀少寫
SSO (小字串優化)短字串零配置、無共享狀態、移動語意友善長字串仍需配置現代 std::string (C++11+)
移動語意轉移擁有權零拷貝、無參考計數需 C++11、來源物件狀態改變現代 C++ 通用最佳實踐

結論:理解 COW 是掌握共享擁有權、參考計數、延遲複製核心概念的絕佳切入點,但現代 C++ 應優先使用 std::string (SSO+移動) 或 std::shared_ptr 管理共享緩衝區。

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