C++ Primer 練習 13.8 要求實作 HasPtr 的複制指派運算子,關鍵程式碼 delete ps; ps = new_ps; 讓許多初學者困惑:ps 指向的記憶體已釋放,為何還能賦值新位址?本文從指標本質、記憶體生命週期、RAII 原則三個層面徹底釐清。
HasPtr& HasPtr::operator=(const HasPtr &hp) {
std::string *new_ps = new std::string(*hp.ps); // 1. 深層複製資料
delete ps; // 2. 釋放舊資源
ps = new_ps; // 3. 指標指向新資源
i = hp.i;
return *this;
}
| 概念 | 說明 | 比喻 |
|---|---|---|
ps (指標變數) | 儲存記憶體位址的「變數」,本身佔用堆疊空間 (通常 8 位元組) | 門牌號碼紙條 |
*ps (指向的物件) | 堆積區的一塊記憶體區域,存放實際資料 (std::string) | 房子本身 |
delete ps | 歸還「房子」給作業系統,「門牌號碼紙條」仍在手上,但內容已失效 | 拆除房子,紙條留著 |
ps = new_ps | 在「門牌號碼紙條」上填寫新房子的地址 | 換張新門牌 |
執行前: ps ──────> [舊 string 物件 @ 0x1000] new_ps ──> [新 string 物件 @ 0x2000] (剛 new 出來) delete ps; 釋放 0x1000 記憶體,ps 仍儲存 0x1000 (懸空指標) ps ──────> [已釋放記憶體 @ 0x1000] ⚠️ new_ps ──> [新 string 物件 @ 0x2000] ps = new_ps; ps 變數內容更新為 0x2000 ps ──────> [新 string 物件 @ 0x2000] ✅ new_ps ──> [新 string 物件 @ 0x2000]
delete ps 再 new,new 拋出 bad_alloc 時,ps 已成懸空指標,物件進入不可恢復狀態。new 成功才 delete 舊資源,確保「要么複製成功,要么維持原狀」,滿足強制例外安全保證。#include
#include
class HasPtr {
std::shared_ptr ps; // 共享擁有權,自動引用計數
int i;
public:
HasPtr(const std::string &s = "") : ps(std::make_shared(s)), i(0) {}
// 編譯器自動產生的 複制建構/指派/解構 皆正確 (Rule of Zero)
// 無需手寫 operator=、~HasPtr()、copy ctor
};
| 誤區 | 澄清 |
|---|---|
delete ps 會把 ps 變成 nullptr | 不會。delete 僅釋放記憶體,指標變數值不變,成為懸空指標。 |
ps = new_ps 會複製字串內容 | 不會。僅複製指標值 (位址),兩指標指向同一物件 (共享)。 |
| 指標變數本身在堆積區 | 區域變數指標在堆疊;new 出來的物件才在堆積。 |
delete ps 釋放的是「被指向的記憶體」,而非「指標變數本身」。指標變數 ps 依然存在於堆疊,隨時可接受新位址賦值。理解「指標與被指物件分離」是掌握 C++ 記憶體管理的關鍵。現代 C++ 建議優先使用 std::shared_ptr/std::unique_ptr 實踐 RAII,徹底避免手動 new/delete 的風險。