Effective C++ 条款17:以独立语句将newed对象置入智能指针

Effective C++ 条款17:以独立语句将newed对象置入智能指针


核心思想 :使用智能指针管理动态分配的对象时,必须确保new操作与智能指针构造在同一独立语句中完成,避免编译器优化顺序导致的内存泄漏。

⚠️ 1. 跨语句初始化的危险性

资源泄漏场景:

cpp 复制代码
// 危险:跨语句初始化智能指针
processWidget(std::shared_ptr<Widget>(new Widget), riskyFunction());

编译器可能的执行顺序:

  1. 执行 new Widget(分配内存)
  2. 调用 riskyFunction()(可能抛出异常)
  3. 构造 shared_ptr(管理资源)

风险分析:

  • 若 riskyFunction() 抛出异常 → 步骤1分配的Widget对象内存泄漏
  • 因 shared_ptr 尚未接管资源 → 无自动释放机制

🚨 2. 解决方案:独立语句初始化

安全初始化模式:

cpp 复制代码
// 正确:独立语句完成资源分配和智能指针构造
std::shared_ptr<Widget> pw = std::make_shared<Widget>(); // 推荐方式

// 或显式new+智能指针构造(同一语句)
std::shared_ptr<Widget> pw(new Widget); // 安全构造

processWidget(pw, riskyFunction()); // 异常安全调用

现代C++最佳实践:

cpp 复制代码
// 优先使用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++标准允许编译器重排函数参数求值顺序(未指定顺序)

cpp 复制代码
// 编译器可能的重排顺序:
1. new Widget        // 分配资源
2. riskyFunction()   // 可能抛出异常
3. shared_ptr构造    // 未执行 → 资源泄漏

自定义RAII类的安全用法:

cpp 复制代码
// 自定义资源管理类
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);    // 异常安全

💡 关键原则总结

  1. 异常安全第一原则
    • 动态资源必须立即被资源管理对象接管
    • new操作与RAII对象构造必须原子化完成
  2. make函数优先原则
    • std::make_shared<>()(C++11)
    • std::make_unique<>()(C++14)
    • 避免显式new操作
  3. 禁用复杂参数表达式
    • 禁止在函数调用参数内组合new与智能指针构造
    • 禁用多步操作初始化智能指针

错误用法重现:

cpp 复制代码
// 危险:可能泄漏资源的写法
processResource(
    std::unique_ptr<Resource>(new Resource), // 风险点
    loadConfig() // 可能抛出异常
);

安全重构方案:

cpp 复制代码
// 方案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());
相关推荐
老歌老听老掉牙4 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
Severus_black5 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl
xiaoye-duck9 小时前
《C++ 项目:手写大模型接入 SDK》DataManager数据管理模块:SQLite持久化存储
开发语言·数据库·c++·sqlite
是main不是漫10 小时前
【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基
c语言·开发语言·c++
小七在进步21 小时前
string的使用(1)
c++·visual studio
白杨尚青1 天前
C++进阶篇(六):map 和 set 的模拟实现——用红黑树封装 mymap/myset(迭代器 + operator[])
开发语言·c++·迭代器·stl·set·map·红黑树
kloc1 天前
聊天室项目入口网关子服务的实现
linux·c++
csdn_aspnet1 天前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
小七在进步1 天前
string的使用(2)
c++
青少儿编程课堂1 天前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:折半搜索优化子集枚举(Meet in the Middle)解析
c++·python·算法·bfs·信息学竞赛