当前位置: 首页 > news >正文

Item17--以独立语句将 `new` 到的对象置入智能指针

💡 条款 17:以独立语句将 new 到的对象置入智能指针

(Store newed objects in smart pointers in standalone statements)

这个条款是关于异常安全 (Exception Safety) 的,它指导我们在初始化智能指针时,应将动态内存分配(new 操作)和智能指针的构造放在一个独立的语句中,以防止资源泄漏。

1. 为什么需要独立语句?(问题所在)

考虑以下函数调用,它在同一条语句中执行了三个操作:

// 假设 logCall 函数可能在内部抛出异常
// 假设 std::shared_ptr 是我们使用的智能指针类型
void processWidget(std::shared_ptr<Widget> spw, int priority);// 危险的代码:在一个语句中执行三个操作
processWidget(std::shared_ptr<Widget>(new Widget), // 操作 1:new WidgetgetPriority()                        // 操作 2:调用 getPriority
); 
// 操作 3:std::shared_ptr 的构造函数

在 C++ 编译器决定执行上述三个操作(new WidgetgetPriority()std::shared_ptr 构造)的顺序时,并不严格要求

  • 顺序 A: 操作 1 (new) → 操作 3 (shared_ptr ctor) → 操作 2 (getPriority)
  • 顺序 B: 操作 2 (getPriority) → 操作 1 (new) → 操作 3 (shared_ptr ctor)
  • 顺序 C: 操作 1 (new) → 操作 2 (getPriority) → 操作 3 (shared_ptr ctor)
  • ...以及其他可能的交错顺序

致命风险:如果编译器选择了类似顺序 C 的执行顺序:

  1. 执行 new Widget堆上内存已分配,但尚未置入智能指针
  2. 执行 getPriority()如果此函数抛出异常(如内存不足、I/O 错误等)。
  3. 异常发生! std::shared_ptr 的构造函数(操作 3)尚未执行,因此没有智能指针来接管这块内存
  4. 程序堆栈展开(Stack Unwinding),函数退出。由于没有智能指针接管,刚才 new 出来的 Widget 内存将永远无法被释放,导致内存泄漏 (Memory Leak)

2. 正确的做法(解决方案)

要避免上述的资源泄漏,解决方案就是将 new 操作和智能指针的构造隔离成一个独立的语句。

// 安全的代码:将 new 到的对象置入智能指针,作为独立语句
std::shared_ptr<Widget> spw(new Widget); // 独立语句 1:确保 spw 立即接管内存processWidget(spw,             // 语句 2:使用已初始化的智能指针getPriority()    
);

🔍 原理分析:

在第一条独立语句 std::shared_ptr<Widget> spw(new Widget); 中,只涉及两个操作:new Widgetstd::shared_ptr 的构造。这两个操作不会被另一个可能抛出异常的函数调用(如 getPriority())打断。一旦 new Widget 执行,std::shared_ptr 就会立即执行其构造函数接管这块内存。如果此时有异常发生,智能指针 spw 已经存在,它会在堆栈展开时被析构,自动释放它所持有的内存,从而保证异常安全

3. 现代 C++ 的最佳实践:使用 std::make_uniquestd::make_shared

自 C++11/14 以来,强烈建议使用工厂函数 std::make_uniquestd::make_shared 来代替直接使用 new。它们不仅更简洁、更高效,而且从根本上解决了条款 17 的问题

A. std::make_unique (C++14 起)

// 现代 C++ 最佳实践
void processWidget(std::unique_ptr<Widget> upw, int priority);processWidget(std::make_unique<Widget>(), // make_unique 在内部完成 new 和 unique_ptr 的构造getPriority()              // 不会存在 new 之后、unique_ptr 构造之前的异常风险
); 

B. std::make_shared (C++11 起)

// 现代 C++ 最佳实践
void processWidget(std::shared_ptr<Widget> spw, int priority);processWidget(std::make_shared<Widget>(), // make_shared 在内部完成 new 和 shared_ptr 的构造getPriority()
); 

优势:

  1. 异常安全: make_sharedmake_unique 在一个函数调用中完成所有操作,保证了原子性,彻底避免了条款 17 中描述的潜在泄漏。
  2. 效率更高 (对于 make_shared): std::shared_ptr<Widget>(new Widget) 需要两次堆内存分配(一次给 Widget 对象,一次给 shared_ptr控制块)。而 std::make_shared<Widget>() 只需一次分配,将对象和控制块放在同一块内存中,效率更高。

总结要点

建议 理由
条款 17 原则 总是将 new 表达式的结果立即赋给智能指针,不要让它在同一行语句中与其他可能抛异常的函数调用混杂。
现代 C++ 最佳实践 优先使用 std::make_uniquestd::make_shared
核心目的 确保在任何函数调用(包括那些可能抛出异常的函数)之间,动态分配的资源都能被 RAII 对象(即智能指针)接管,实现异常安全,防止资源泄漏。
http://www.jsqmd.com/news/115952/

相关文章:

  • Item17--以独立语句将 `new` 到的对象置入智能指针
  • 3433. 统计用户被提及情况
  • Item19--设计 class 犹如设计 type
  • 国外软件,安装即时专业版!
  • Item19--设计 class 犹如设计 type
  • basic_regex
  • c++狼人杀
  • 宠物识别丨基于弱监督学习的宠物视频内容自动标注技术实践 - 指南
  • 朴易天下:道家修行的专业术语分享
  • 个人投资者的落地路径:从“说人话,做量化”到实盘前的三道关
  • 神经网络中的 block 和 module
  • item13--使用对象管理资源
  • 深入解析:蓝桥杯基础算法精讲:模拟与高精度运算实战指南
  • item12-- 拷贝一个对象的所有组成部分
  • sub_match
  • sub_match
  • 抽奖机随机号码生成:3 种算法实现 + 测试全解析(附完整代码)
  • 【零基础精通】Python 字符串全解析:从字符序列到不可变对象的深度构建
  • item14--谨慎考虑资源管理类的拷贝行为
  • python django flask酒店客房管理系统数据可视化分析系统_gq8885n3--论文md5
  • python django flask鹿幸公司员工食堂在线点餐餐饮餐桌预约管理系统的设计与实现_utcnqqs0--论文
  • error_code
  • 虚拟化初步了解
  • Miloco 深度打通 Home Assistant,实现设备级精准控制
  • 好用的大型牛场水滴粉碎机技术强的
  • set_value
  • 日记1217
  • function的类型擦除
  • function bind
  • 日记12,19