題目
class Foo {
public:
Foo(A* a, B* b);
~Foo() { delete a; delete b; }
// 實現拷貝建構函數
// A 和 B 皆為 CopyConstructible
private:
A* a;
B* b;
};
標準答案(C++98 風格)
Foo::Foo(const Foo& other) {
this->a = new A(*other.a);
this->b = new B(*other.b);
}
深度解析:為什麼這樣寫?
- 深拷貝語意:
Foo 擁有 A、B 的所有權(解構函數 delete),拷貝必須建立**新物件**,而非共享指標(淺拷貝會導致雙重釋放)。
- 異常安全問題:若
new A(*other.a) 成功,new B(*other.b) 拋出異常 → a 已分配卻未賦值給成員 → **記憶體洩漏**。
- 強異常安全保證:採用 **Copy-and-Swap** 或 **RAII 智慧指標**。
現代 C++ 最佳實踐
方案一:std::unique_ptr(推薦,C++11+)
#include
class Foo {
public:
Foo(std::unique_ptr a, std::unique_ptr b)
: a_(std::move(a)), b_(std::move(b)) {}
// 編譯器自動生成的拷貝建構函數 = delete(unique_ptr 不可拷貝)
// 若需拷貝,提供 clone:
Foo(const Foo& other)
: a_(other.a_ ? std::make_unique(*other.a_) : nullptr),
b_(other.b_ ? std::make_unique(*other.b_) : nullptr) {}
// 移動建構/賦值預設即可
private:
std::unique_ptr a_;
std::unique_ptr b_;
};
方案二:Copy-and-Swap(C++98 相容)
class Foo {
public:
Foo(A* a, B* b) : a_(a), b_(b) {}
Foo(const Foo& other) : a_(new A(*other.a_)), b_(new B(*other.b_)) {}
Foo& operator=(Foo other) { // 傳值參數觸發拷貝建構
swap(*this, other);
return *this;
}
~Foo() { delete a_; delete b_; }
friend void swap(Foo& lhs, Foo& rhs) noexcept {
std::swap(lhs.a_, rhs.a_);
std::swap(lhs.b_, rhs.b_);
}
private:
A* a_;
B* b_;
};
- 賦值運算子傳值參數 → 自動調用拷貝建構函數建立臨時副本 →
swap 交換資源 → 臨時副本解構釋放舊資源。
- 若拷貝建構拋出異常,原物件**完全不變**(強異常安全)。
關鍵點總結
| 議題 | 錯誤寫法 | 正確寫法 |
| 淺拷貝 | a = other.a; b = other.b; | 深拷貝 new A(*other.a) |
| 異常洩漏 | 分步 new 直接賦值成員 | Copy-and-Swap / 智慧指標 |
| 自賦值 | 未檢查 this == &other | Copy-and-Swap 天然免疫 |
| Rule of Three/Five | 只寫拷貝建構,缺賦值/解構 | 三/五個函數齊全或 =default/delete |