假日無 deadline 壓力,閱讀同事代碼時發現:
class T {
public:
T& ref() { return *this; }
};
void f(T&);
int main() {
f(T().ref()); // 編譯通過
// f(T()); // ❌ 編譯錯誤:無法將非常量參考綁定到臨時物件
}
初見以為 f(T()) 應可行,卻報錯「綁定非常量參考到臨時物件」。刪除 .ref() 卻錯誤,保留卻通過——這違背直覺。
「禁止綁定非常量參考到臨時物件,是為防止隱蔽 bug。若允許,經由轉換運算子產生的臨時物件被修改後銷毀,原物件卻未變,極易誤導程式設計師。」
但為何 f(T().ref()) 合法?
「必須多思考一步:
T()建立右值;右值僅能綁定常量、非 volatile 參考;但函數返回參考型別,其返回值為左值。故ref()返回左值,可綁定非常量參考。」
若 f(T() = T()) 合法?賦值運算子返回 T&,同理可繞過檢查。但大師警告:
「此技巧極危險,至少有一種情況導致未定義行為:物件生命週期管理。」
構造函數持有參考成員:
class U {
T& t_;
public:
U(T& t) : t_(t) {}
};
// 使用賦值技巧建立臨時物件並傳遞
{
U u( (T() = T()) ); // 臨時物件在 U 建構結束時銷毀
// u.t_ 成為野參考 (dangling reference)!
}
區塊結束時,臨時 T 物件銷毀,U 內部參考懸空。後續存取 u.t_ 即為未定義行為——如同從未存在的物件。
{
T tmp; // 命名物件,生命週期至區塊結束
U u(tmp); // 綁定有效物件
// 安全使用 u
}
| 規則 | 說明 |
|---|---|
| 臨時物件 (右值) | 僅能綁定 const T& 或 T&& (C++11 右值參考) |
| 函數返回參考 | 返回值為左值,可綁定 T& |
| 賦值運算子 | 返回 T&,屬左值 |
| 參考成員初始化 | 必須綁定生命週期超過擁有者的物件 |
| 延長生命週期 | 唯一方式:綁定到 const T& (臨時物件生命延長至參考銷毀) 或命名變數 |
std::shared_ptr/std::weak_ptr 管理共享所有權。T&&) 實現移動語義,而非濫用臨時物件綁定技巧。-Wall -Wextra -Werror / /W4) 捕獲懸空參考風險。註腳:ISO/IEC 14882:1998(E) §5.2.3, §8.5.3, §3.10。
作者:Jim Hyslop,Leitch Technology International 高級軟體架構師。