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

Effective C++ 条款52:写了placement new也要写placement delete

Effective C++ 条款52:写了placement new也要写placement delete


核心思想当你重载一个带有额外参数的operator new(即“placement new”)时,必须同时重载带有完全相同额外参数的operator delete。否则,当对象的构造函数在placement new分配的内存上抛出异常时,会发生微妙而严重的内存泄漏。同时,要注意避免无意中掩盖全局的正常版本。

⚠️ 1. 问题的根源:构造函数异常

1.1 C++的内存分配与构造流程

  1. operator new 分配原始内存
  2. 在该内存上调用构造函数
  3. 如果步骤2的构造函数抛出异常,运行时系统必须能够自动释放在步骤1中分配的内存,以避免内存泄漏。

1.2 运行时系统的职责
当构造函数抛出异常,运行时系统需要找到与调用operator new签名完全相同operator delete来释放内存。如果找不到,则什么也不做,导致内存泄漏。


🚨 2. placement new/delete 的匹配规则

2.1 标准placement new
最常用的placement new是接收一个void*指针参数,用于在指定地址构造对象。

// 标准库提供的placement new
void* operator new(std::size_t, void* pMemory) noexcept; 
// 对应的placement delete
void operator delete(void* pMemory, void* pLocation) noexcept; 

2.2 自定义placement new
你可以定义接收任意额外参数的operator new

class Widget {
public:// 自定义的placement new(额外带一个int参数)static void* operator new(std::size_t size, int extraParam) {std::cout << "Custom placement new called with: " << extraParam << std::endl;return ::operator new(size); // 这里为了简单,仍使用全局new}// ⚠️ 必须提供对应的placement delete!// 参数列表:(size_t) + 与placement new完全相同的额外参数列表(int)static void operator delete(void* pMemory, int extraParam) noexcept {std::cout << "Custom placement delete called with: " << extraParam << std::endl;::operator delete(pMemory);}// ... 通常的operator delete也不能少static void operator delete(void* pMemory) noexcept;
};

使用示例与潜在问题

try {// 调用自定义的placement newWidget* pw = new (100) Widget; // 传递额外参数100// ... 如果Widget构造函数在此处抛出异常...// 运行时系统会自动调用 operator delete(pw, 100)
} catch (...) {// 如果没有定义 operator delete(void*, int),内存将在此泄漏
}

⚖️ 3. 避免名称隐藏问题

3.1 默认的名称隐藏(Name Hiding)
在类中声明任何operator new(包括placement版本)都会隐藏全局的、标准的operator new。这意味着new Widget会编译失败,因为找不到标准的operator new(size_t)

3.2 解决方案:提供标准版本并using基类版本
为了同时使用自定义placement new和标准new,必须在类中同时声明它们,并使用using引入基类的operator new以确保继承链正常工作。

class StandardNewDeleteBase {
public:// 提供标准new/delete的入口static void* operator new(std::size_t size) {return ::operator new(size);}static void operator delete(void* pMemory) noexcept {::operator delete(pMemory);}// ... 可补充new[]和delete[]
};class Widget : public StandardNewDeleteBase {
public:using StandardNewDeleteBase::operator new;using StandardNewDeleteBase::operator delete;// 自定义placement newstatic void* operator new(std::size_t size, int extraParam) {// ... 自定义实现return ::operator new(size);}// 对应的placement deletestatic void operator delete(void* pMemory, int extraParam) noexcept {// ... 自定义实现::operator delete(pMemory);}
};// 现在以下调用都是合法的:
Widget* pw1 = new Widget;           // 正确,调用了被using引入的标准new
Widget* pw2 = new (100) Widget;     // 正确,调用了自定义的placement new

💡 关键设计原则

  1. 成对实现
    每一个自定义的placement operator new(即任何非标准的、带有额外参数的new)都必须有一个参数列表完全匹配的placement operator delete伴随左右。这是防止构造函数异常导致内存泄漏的唯一安全网。
  2. 理解调用时机
    placement operator delete 只有在与之匹配的placement operator new成功分配内存,但后续的对象构造函数抛出异常时,才会被运行时系统自动调用。如果你正常地delete一个对象,即使它是用placement new创建的,被调用的也永远是普通的operator delete(void*)
  3. 管理名称空间
    在类内部重载operator new/delete会隐藏全局版本。务必使用using ::operator new;using ::operator delete;(或使用如上面示例中的基类技巧)来确保所有需要的版本都可见,保持代码的灵活性。

进阶提示:大小感知的placement delete (C++14+)
现代C++允许placement delete也接受大小参数,这在实现自定义内存池时非常有用,可以优化释放操作。

class Widget {// ...// 大小感知的placement delete (更优)static void operator delete(void* pMemory, std::size_t size, int extraParam) noexcept {std::cout << "Sized placement delete. Size: " << size << ", Param: " << extraParam << std::endl;::operator delete(pMemory);}
};

编译器会优先选择最匹配的版本。提供大小感知的版本通常是最佳实践。

总结
placement new和placement delete是“成对出现”的生死之交。定义任何形式的placement operator new(即带有额外参数的new)时,都必须毫不例外地定义与之精确匹配的placement operator delete。这是确保在对象构造失败时系统能自动清理内存、避免资源泄漏的黄金法则。此外,要小心类内重载带来的名称隐藏问题,通过使用using声明或继承体系来确保标准的new/delete版本依然可用。遵守此条款,你才能安全地利用placement new的强大功能进行底层内存管理。

http://www.xdnf.cn/news/18484.html

相关文章:

  • ES常用查询命令
  • SQL详细语法教程(七)核心优化
  • ubuntu系统上的conda虚拟环境导出方便下次安装
  • PiscCode使用MediaPipe Face Landmarker实现实时人脸特征点检测
  • YOLO11 到 C++ 落地全流程:ONNX 导出、NMS 判别与推理实战
  • Java 通过 m3u8 链接下载所有 ts 视频切片并合并转换为 mp4 格式
  • 《GPT-OSS 模型全解析:OpenAI 回归开源的 Mixture-of-Experts 之路》
  • 深入理解MySQL Ⅳ -- SQL性能分析工具
  • 文件操作NIO Files的简单使用
  • InfluxDB 查询性能优化实战(一)
  • SCAU学习笔记 - 自科三面前端方向实战演示
  • Disruptor核心接口EventHandler解析
  • 【Techlog】01入门-井筒数据整合软件的基本认识
  • C5.6:双电源发射极偏置、特殊类偏置、PNP型偏置电路
  • ODPS 十五周年实录 | 为 AI 而生的数据平台
  • 机器学习(Machine Learning, ML)
  • 项目1其二(验证码、jwt)
  • 《算法导论》第 33 章 - 计算几何学
  • 关于uniappx注意点1 - 鸿蒙app
  • 3ds Max 流体模拟终极指南:从创建到渲染,打造真实液体效果
  • 模拟tomcat接收GET、POST请求
  • 元宇宙的硬件设备:从 VR 头显到脑机接口
  • 【数据库】Oracle学习笔记整理之六:ORACLE体系结构 - 重做日志文件与归档日志文件(Redo Log Files Archive Logs)
  • Navicat Premium v17.2.3
  • Advanced Math Math Analysis |01 Limits, Continuous
  • 力扣hot100:最大子数组和的两种高效方法:前缀和与Kadane算法(53)
  • 学习设计模式《二十三》——桥接模式
  • uniapp:h5链接拉起支付宝支付
  • 有向图(Directed Graph)和有向无环图(Directed Acyclic Graph,DAG)代码实践
  • pcl求平面点云的边界凸包点