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

拷貝對象時勿忘其每一個成分:複製構造與賦值運算符的完整性

     編輯:關於C++

問題背景

編譯器默認提供逐位複製的拷貝構造函數和賦值運算符。當類管理動態資源(指針、句柄、文件描述符等)時,**必須自定義**,且要確保**所有成員**正確拷貝/賦值。

反面教材:遺漏新增成員


class Event {
public:
    Event(int id) : eventID_(id) {}
    Event(const Event& rhs) : eventID_(rhs.eventID_) {}  // 只拷貝 eventID_
    Event& operator=(const Event& rhs) {
        eventID_ = rhs.eventID_;
        return *this;
    }
private:
    int eventID_;
};

後來需求變更,添加時間戳:


class Event {
    // ... 原有代碼 ...
private:
    int eventID_;
    DateTime timestamp_;  // 新增成員
};

**災難發生**:拷貝構造/賦值函數未更新,`timestamp_` 被默認逐位複製(若為類類型調用其拷貝構造,若為指針則淺拷貝),導致雙重釋放、邏輯錯誤。

正確做法:統一維護

方案 1:手動同步更新(易忘)


Event(const Event& rhs) : eventID_(rhs.eventID_), timestamp_(rhs.timestamp_) {}
Event& operator=(const Event& rhs) {
    eventID_ = rhs.eventID_;
    timestamp_ = rhs.timestamp_;
    return *this;
}

方案 2:委托賦值運算符(推薦 C++11 前)


Event(const Event& rhs) { *this = rhs; }  // 調用賦值運算符
Event& operator=(const Event& rhs) {
    if (this != &rhs) {  // 自賦值檢查
        eventID_ = rhs.eventID_;
        timestamp_ = rhs.timestamp_;
    }
    return *this;
}

方案 3:Copy-and-Swap 惯用法(強異常安全、碼簡潔)


#include <algorithm>  // std::swap
#include <utility>    // std::move (C++11)

class Event {
public:
    Event(int id) : eventID_(id), timestamp_(DateTime::now()) {}
    
    // 拷貝構造
    Event(const Event& rhs) : eventID_(rhs.eventID_), timestamp_(rhs.timestamp_) {}
    
    // 移動構造 (C++11)
    Event(Event&& rhs) noexcept : eventID_(rhs.eventID_), timestamp_(std::move(rhs.timestamp_)) {}
    
    // 統一賦值:傳值參數自動觸發拷貝/移動構造
    Event& operator=(Event rhs) noexcept {  // by value!
        swap(*this, rhs);
        return *this;
    }
    
    friend void swap(Event& a, Event& b) noexcept {
        using std::swap;
        swap(a.eventID_, b.eventID_);
        swap(a.timestamp_, b.timestamp_);
    }
private:
    int eventID_;
    DateTime timestamp_;
};

優勢:

  • **零代碼重複**:拷貝/移動/賦值邏輯集中在 `swap`
  • **強異常安全**:參數構造失敗不修改 `*this`;`swap` 不拋異常
  • **自賦值安全**:傳值參數已是副本,無需檢查

檢查清單:添加/刪除成員時必做

  1. 拷貝構造函數初始化列表
  2. 移動構造函數 (C++11)
  3. 拷貝賦值運算符 / 統一賦值運算符
  4. 移動賦值運算符 (C++11,若非 Copy-and-Swap)
  5. 析構函數(資源釋放)
  6. `swap` 友元函數

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

  • Rule of Zero:優先用 `std::unique_ptr`、`std::string`、`std::vector` 等 RAII 類型管理資源,**完全不寫** 拷貝/移動/析構,編譯器合成即正確
  • Rule of Five:若必須手動管理資源,需顯式聲明/刪除/定義五個特殊函數:析構、拷貝構造、移動構造、拷貝賦值、移動賦值

// Rule of Zero 範例
class Event {
    int eventID_;
    std::string description_;  // 自動管理內存
    std::unique_ptr<DateTime> timestamp_;  // 獨占所有權
    // 無需聲明任何特殊函數!
};

總結

「拷貝對象時勿忘其每一個成分」不只是口號,而是維護類不變量的核心紀律。採用 **Copy-and-Swap** 或 **Rule of Zero** 讓編譯器幫你守住這條底線。

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