You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中第二种new操作为何无法调用类重载的operator delete?

C++ new表达式与直接调用operator new的差异原理

示例测试代码如下:

class Test {
public:
    Test() {
        throw "error";// 主动抛出异常
    }
    void operator delete(void* ptr, string msg) { // 重载版本0
        cout << msg << endl;
    }
    void operator delete(void* ptr) { // 重载版本1
        cout << "no msg" << endl;
    }
};


int main() {
    try {
        Test* t0 = new Test; // 打印“no msg”
        Test* t1 = static_cast<Test*>(::operator new(sizeof(Test))); // 无打印
    }
    catch(...){}
}

两种写法产生运行差异的根本原因是:new Test是编译器层面实现的new表达式语法规则,而::operator new(...)只是一次普通的全局函数调用,二者执行逻辑完全不在同一层面。VS中跳转定位到同一个函数声明,只是因为new表达式默认复用全局operator new作为内存分配的底层入口,不代表二者行为等价。

1. 两种写法的完整执行流程对比

  • 对于Test* t0 = new Test;这个new表达式,编译器会自动生成全套执行逻辑:
    • 内存分配阶段:优先查找Test类内是否重载operator new,有则调用类内版本,无则调用全局operator new分配足够存放Test对象的内存
    • 对象构造阶段:在分配好的内存地址上调用Test的构造函数
    • 异常兜底阶段:如果构造函数抛出异常,编译器自动生成的代码会查找Test类内签名匹配的operator delete,释放已经分配的内存,避免内存泄漏,之后再把异常向外传播
    • 构造成功后,将类型匹配的对象地址赋值给t0指针
  • 对于Test* t1 = static_cast<Test*>(::operator new(sizeof(Test)));这个写法,本质只是一次普通函数调用:
    • 前缀::强制指定调用全局作用域下的operator new函数,完全忽略Test类内重载的operator new/operator delete
    • 这个函数仅按传入的sizeof(Test)字节数申请一块原始未初始化的内存,返回void*类型的内存地址
    • 整个过程不会调用Test的构造函数,不会生成任何异常场景下的自动内存回收逻辑,也不关心这块内存后续用来存储什么类型的数据——你把返回值强转成任意类型指针都可以

2. 运行结果差异的具体解释

对应示例代码的运行现象:

  • 第一种写法中,Test构造函数抛出异常时,编译器自动触发异常兜底逻辑,在Test类作用域内查找匹配的operator delete:因为没有额外参数传入,匹配到无参的重载版本1,因此打印no msg,这是C++标准规定的new表达式必须具备的异常安全保证。
  • 第二种写法中,代码仅申请了一块原始内存,全程没有调用Test的构造函数,根本不存在“构造对象失败”的场景;加上调用时强制指定了全局operator new,不会关联Test类内重载的delete版本,自然不会有任何打印输出。

补充说明:如果手动调用operator new拿到原始内存后,再用placement new手动调用构造函数,一旦构造抛出异常,需要开发者自己手动调用匹配的operator delete释放内存,编译器不会自动帮你完成这步回收——这部分逻辑本来就是new表达式帮你封装好的,绕过new表达式就需要自己处理所有边界情况。

内容的提问来源于stack exchange,提问作者Young_cun_yi_ba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:48:19