Effective C++ 条款17:以独立语句将newed对象置入智能指针
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());