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

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.dtcms.com/a/341142.html

相关文章:

  • 使用acme.sh自动申请AC证书,并配置自动续期,而且解决华为云支持问题,永久免费自动续期!
  • Spring Boot 定时任务与 xxl-job 灵活切换方案
  • 层在init中只为创建线性层,forward的对线性层中间加非线性运算。且分层定义是为了把原本一长个代码的初始化和运算放到一个组合中。
  • B站 韩顺平 笔记 (Day 24)
  • C++ std::optional 深度解析与实践指南
  • 当 AI 开始 “理解” 情绪:情感计算如何重塑人机交互的边界
  • linux报permission denied问题
  • Advanced Math Math Analysis |01 Limits, Continuous
  • uniapp打包成h5,本地服务器运行,路径报错问题
  • PyTorch API 4
  • 使数组k递增的最少操作次数
  • 路由器的NAT类型
  • 确保测试环境一致性与稳定性 5大策略
  • AI 效应: GPT-6,“用户真正想要的是记忆”
  • 获取本地IP地址、MAC地址写法
  • SQL 中大于小于号的表示方法总结
  • Bitcoin有升值潜力吗
  • 《代码沙盒深度实战:iframe安全隔离与实时双向通信的架构设计与落地策略》
  • 在SQL中使用大模型时间预测模型TimesFM
  • Mybatis执行SQL流程(五)之MapperProxy与MapperMethod
  • zoho crm api 无法修改富文本字段的原因:api 版本太低
  • 23种设计模式——构建器模式(Builder Pattern)详解
  • Spring Boot Controller 使用 @RequestBody + @ModelAttribute 接收请求
  • 车联网(V2X)中万物的重新定义---联网汽车新时代
  • Dubbo 的 Java 项目间调用的完整示例
  • 分析NeRF模型中颜色计算公式中的参数
  • Paraformer实时语音识别中的碎碎念
  • RuntimeError: Dataset scripts are no longer supported, but found wikipedia.py
  • 车辆订单状态管理的优化方案:状态机设计模式
  • 从ioutil到os:Golang在线客服聊天系统文件读取的迁移实践