Effective C++ 條款17:以獨立語句將newed對象置入智能指針
核心思想:使用智能指針管理動態分配的對象時,必須確保new
操作與智能指針構造在同一獨立語句中完成,避免編譯器優化順序導致的內存泄漏。
?? 1. 跨語句初始化的危險性
資源泄漏場景:
// 危險:跨語句初始化智能指針
processWidget(std::shared_ptr<Widget>(new Widget), riskyFunction());
編譯器可能的執行順序:
- 執行
new Widget
(分配內存) - 調用
riskyFunction()
(可能拋出異常) - 構造
shared_ptr
(管理資源)
風險分析:
- 若
riskyFunction()
拋出異常 → 步驟1分配的Widget
對象內存泄漏 - 因
shared_ptr
尚未接管資源 → 無自動釋放機制
🚨 2. 解決方案:獨立語句初始化
安全初始化模式:
// 正確:獨立語句完成資源分配和智能指針構造
std::shared_ptr<Widget> pw = std::make_shared<Widget>(); // 推薦方式// 或顯式new+智能指針構造(同一語句)
std::shared_ptr<Widget> pw(new Widget); // 安全構造processWidget(pw, riskyFunction()); // 異常安全調用
現代C++最佳實踐:
// 優先使用std::make_shared(C++11+)
auto pw = std::make_shared<Widget>(); // 需自定義刪除器時:
auto pw = std::shared_ptr<Widget>(new Widget, customDeleter);
?? 3. 關鍵原則與注意事項
原則 | 說明 | 違反后果 |
---|---|---|
單語句構造原則 | new 操作與智能指針構造必須在同一獨立語句完成 | 資源泄漏風險 |
優先make_shared/make_unique | 使用工廠函數保證異常安全(C++11/14) | 消除顯式new |
避免函數參數內構造 | 禁止在函數調用參數列表內直接new +智能指針構造 | 編譯器重排風險 |
擴展至所有資源管理類 | 規則同樣適用于自定義RAII類 | 通用資源安全原則 |
編譯器優化陷阱:
C++標準允許編譯器重排函數參數求值順序(未指定順序)
// 編譯器可能的重排順序: 1. new Widget // 分配資源 2. riskyFunction() // 可能拋出異常 3. shared_ptr構造 // 未執行 → 資源泄漏
自定義RAII類的安全用法:
// 自定義資源管理類
class DBConnection { /* ... */ };
class DBHandler {
public:explicit DBHandler(DBConnection* conn) : conn_(conn) {}~DBHandler() { conn_->close(); }
private:DBConnection* conn_;
};// 安全初始化:
DBConnection* dbc = new DBConnection; // 危險:分離語句
DBHandler handler(dbc); // 可能泄漏// 正確:同一語句完成
DBHandler handler(new DBConnection); // 異常安全
💡 關鍵原則總結
- 異常安全第一原則
- 動態資源必須立即被資源管理對象接管
new
操作與RAII對象構造必須原子化完成
- make函數優先原則
std::make_shared<>()
(C++11)std::make_unique<>()
(C++14)- 避免顯式
new
操作
- 禁用復雜參數表達式
- 禁止在函數調用參數內組合
new
與智能指針構造 - 禁用多步操作初始化智能指針
- 禁止在函數調用參數內組合
錯誤用法重現:
// 危險:可能泄漏資源的寫法 processResource(std::unique_ptr<Resource>(new Resource), // 風險點loadConfig() // 可能拋出異常 );
安全重構方案:
// 方案1:獨立語句構造(基礎) auto res = std::unique_ptr<Resource>(new Resource); processResource(std::move(res), loadConfig());// 方案2:make_unique(推薦,C++14+) auto res = std::make_unique<Resource>(); processResource(std::move(res), loadConfig());// 方案3:延遲調用(異常安全封裝) auto callProcess = [](auto&&... args) {auto res = std::make_unique<Resource>();processResource(std::move(res), std::forward<decltype(args)>(args)...); }; callProcess(loadConfig());