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

C++ 類別物件複製機制深度解析:複製建構子、預設行為與自訂實作

     編輯:VC 教學

前言

類別物件的複製是 C++ 核心語意之一。內建型別逐位複製即可,但類別擁有成員變數、動態資源、不變式,複製語意必須由複製建構子 定義。本文詳細說明預設行為、自訂實作、深淺複製抉擇與現代最佳實踐。

一、複製建構子語法與簽名

class MyClass {
public:
    // 標準形式:const 參考
    MyClass(const MyClass& other);

    // 罕見非 const 形式 (舊代碼/特殊需求)
    MyClass(MyClass& other);
};
  • 名稱必須與類別相同
  • 參數必須為同類別的參考 (傳值會無限遞迴)
  • 通常加 const,表示不修改來源物件
  • 無回傳型別

二、預設複製建構子:逐成員複製

若類別未宣告任何複製建構子,編譯器自動產生一個,執行逐成員複製:

  • 非靜態資料成員:遞迴呼叫其複製建構子
  • 陣列成員:逐元素複製
  • 基類子物件:呼叫基類複製建構子

預設行為適用場景

  • 所有成員為內建型別、標準庫容器、智慧指標、std::string 等擁有正確複製語意的型別
  • 類別不擁有獨佔資源 (原始指標、檔案控制代碼、Socket、互斥鎖等)

範例:預設行為正確

class Point {
    int x, y; // 內建型別,逐位複製即正確
public:
    Point(int x_, int y_) : x(x_), y(y_) {}
    // 編譯器產生的複製建構子完美運作
};

三、必須自訂複製建構子的情況

情況問題解決方向
擁有原始指標成員預設淺複製 → 雙重釋放/記憶體洩漏深複製 (配置新記憶體複製內容) 或 共享擁有權 (引用計數/智慧指標)
擁有獨佔資源控制代碼複製後雙方關閉同一資源禁止複製 (=delete) 或 移動語意轉移擁有權
需維護不變式逐成員複製可能破壞類別約束自訂邏輯驗證/修正
基類為多型/虛解構子切片問題禁止複製,改用 clone() 虛函數

四、自訂複製建構子範例:深複製

class StringBuffer {
    char* data;
    size_t len;
public:
    StringBuffer(const char* s = "") {
        len = strlen(s);
        data = new char[len + 1];
        memcpy(data, s, len + 1);
    }

    // 複製建構子:深複製
    StringBuffer(const StringBuffer& other) {
        len = other.len;
        data = new char[len + 1];
        memcpy(data, other.data, len + 1);
        printf("複製建構子被呼叫\n");
    }

    // 複製指派運算子 (三大法則)
    StringBuffer& operator=(const StringBuffer& other) {
        if (this != &other) { // 自我指派檢查
            delete[] data;
            len = other.len;
            data = new char[len + 1];
            memcpy(data, other.data, len + 1);
        }
        return *this;
    }

    ~StringBuffer() { delete[] data; }
};

五、現代 C++ 最佳實踐:Rule of Five / Rule of Zero

Rule of Five (C++11)

若類別管理資源,需宣告五個特殊成員函數:

class ResourceHolder {
    Resource* ptr;
public:
    ResourceHolder() : ptr(new Resource) {}
    ~ResourceHolder() { delete ptr; }

    // 複製建構子
    ResourceHolder(const ResourceHolder& other) : ptr(new Resource(*other.ptr)) {}

    // 複製指派
    ResourceHolder& operator=(const ResourceHolder& other) {
        if (this != &other) { *ptr = *other.ptr; }
        return *this;
    }

    // 移動建構子
    ResourceHolder(ResourceHolder&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; }

    // 移動指派
    ResourceHolder& operator=(ResourceHolder&& other) noexcept {
        if (this != &other) { delete ptr; ptr = other.ptr; other.ptr = nullptr; }
        return *this;
    }
};

Rule of Zero (推薦)

盡可能不寫任何特殊成員函數,改用 RAII 型別封裝資源:

class ModernClass {
    std::unique_ptr ptr;        // 獨佔擁有權,不可複製,可移動
    std::shared_ptr shared;     // 共享擁有權,可複製
    std::vector buffer;              // 自動管理記憶體
    std::string name;                     // 自動管理字串
    // 編譯器自動產生正確的複製/移動/解構行為
};

六、禁止複製:= delete

class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    // 可保留移動語意
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

七、常見錯誤與除錯技巧

錯誤徵兆檢測方法
淺複製導致雙重 free程式在解構時崩潰 (double free)Valgrind/AddressSanitizer、啟用 CRT Debug Heap
忘記複製指派運算子a = b 後資源洩漏/雙重釋放靜態分析 (Cppcheck, Clang-Tidy) 警告
基類複製建構子非虛擬多型物件切片Code Review、單元測試驗證型別完整性
自我指派未檢查指派給自己時資源提前釋放單元測試 obj = obj

結語

複製建構子定義了類別的值語意。現代 C++ 強調以 RAII 替代手動資源管理,讓編譯器自動合成正確操作 (Rule of Zero)。若必須手寫,嚴格遵循 Rule of Five,善用 = default/= delete 表達意圖,並以 sanitizer 驗證記憶體安全。

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