編譯器默認提供逐位複製的拷貝構造函數和賦值運算符。當類管理動態資源(指針、句柄、文件描述符等)時,**必須自定義**,且要確保**所有成員**正確拷貝/賦值。
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_` 被默認逐位複製(若為類類型調用其拷貝構造,若為指針則淺拷貝),導致雙重釋放、邏輯錯誤。
Event(const Event& rhs) : eventID_(rhs.eventID_), timestamp_(rhs.timestamp_) {}
Event& operator=(const Event& rhs) {
eventID_ = rhs.eventID_;
timestamp_ = rhs.timestamp_;
return *this;
}
Event(const Event& rhs) { *this = rhs; } // 調用賦值運算符
Event& operator=(const Event& rhs) {
if (this != &rhs) { // 自賦值檢查
eventID_ = rhs.eventID_;
timestamp_ = rhs.timestamp_;
}
return *this;
}
#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_;
};
優勢:
// Rule of Zero 範例
class Event {
int eventID_;
std::string description_; // 自動管理內存
std::unique_ptr<DateTime> timestamp_; // 獨占所有權
// 無需聲明任何特殊函數!
};
「拷貝對象時勿忘其每一個成分」不只是口號,而是維護類不變量的核心紀律。採用 **Copy-and-Swap** 或 **Rule of Zero** 讓編譯器幫你守住這條底線。