C++中new和delete的多重面孔:operator new、new operator与placement new解析

《More Effective C++:35个改善编程与设计的有效方法》
读书笔记:了解各种不同意义的new和delete
C++中new和delete的多重面孔:operator new、new operator与placement new解析_第1张图片

在C++的内存管理体系中,newdelete看似简单,实则隐藏着多层逻辑。许多开发者对 new operatoroperator newplacement new 的区别感到困惑。本文将逐层拆解这些概念,帮你掌握内存分配与对象构造的底层逻辑。

一、new operator:语言内置的“双任务”操作符

我们日常写的 new Type(...) 其实是 new operator(新表达式),它的行为由语言内置,不可直接修改。其核心工作分为两步:

  1. 分配内存:调用 operator new 函数,申请足够容纳 Type 对象的内存;
  2. 构造对象:调用 Type 的构造函数,初始化刚分配的内存。

示例

string* ps = new string("Memory Management");

编译器会隐式生成类似以下逻辑:

// 1. 分配内存(调用 operator new)
void* raw_memory = operator new(sizeof(string));  
// 2. 构造对象(编译器通过 placement new 实现)
string* ps = new (raw_memory) string("Memory Management");  

二、operator new:可重载的内存分配函数

operator new普通函数,负责纯粹的内存分配,不涉及构造函数。它的原型为:

void* operator new(size_t size);
关键特性:
  • 可定制性:可全局或类内重载,实现自定义内存分配策略(如内存池、统计分配次数);
  • 直接调用:仅分配内存,返回未初始化的 void*,需后续手动构造对象。

示例

// 仅分配内存,无构造函数调用
void* raw_memory = operator new(sizeof(string));  

三、placement new:在指定内存上构造对象

placement new 是 operator new 的特殊重载版本,允许在已有的内存地址上构造对象,跳过“分配内存”步骤。

核心逻辑:
  • 原型(标准库实现):
    void* operator new(size_t, void* location) {  
        return location; // 直接返回传入的内存地址,不分配新内存  
    }  
    
  • 使用场景
    • 内存池:预先分配大块内存,后续在其上构造对象(减少分配开销);
    • 共享内存/内存映射IO:对象必须位于特定地址。
使用步骤:
  1. 准备原始内存:可以是栈内存、共享内存等(需保证内存大小和对齐正确);
  2. 调用 placement new 构造对象
  3. 手动调用析构函数(因 delete 会调用 operator delete,而此处内存可能非 operator new 分配,故不能直接 delete);
  4. 释放原始内存(若内存是动态分配的)。

示例

class Widget {  
public:  
    Widget(int size) {}  
    ~Widget() {}  
};  

// 步骤1:准备栈内存(也可是共享内存等)  
alignas(Widget) char buffer[sizeof(Widget)];  

// 步骤2:在 buffer 上构造 Widget  
Widget* pw = new (buffer) Widget(10);  

// 步骤3:手动析构(必须!否则析构函数不会被调用)  
pw->~Widget();  

// 步骤4:若 buffer 是动态分配,需释放(此处栈内存自动释放,故省略)  

注意:使用 placement new 需包含头文件

四、delete的对称逻辑:delete operator与operator delete

delete 操作符(delete operator)与 new 对称,也分两步:

  1. 析构对象:调用对象的析构函数;
  2. 释放内存:调用 operator delete 函数释放内存。
示例:
delete ps;  

编译器隐式生成:

ps->~string(); // 析构对象  
operator delete(ps); // 释放内存  
特殊情况:placement new构造的对象

若对象由 placement new 构造(内存非 operator new 分配,如栈内存、共享内存),不能直接用 delete,否则 operator delete 会错误释放内存。正确流程:

// 假设 pw 构造在共享内存上  
pw->~Widget(); // 手动析构  
freeShared(pw); // 释放共享内存(而非 operator delete)  

五、数组的处理:new[]与delete[]

当用 new Type[size] 分配数组时,实际调用的是 数组版 new operator,流程为:

  1. 调用 operator new[] 分配内存(可重载);
  2. 为数组每个元素调用默认构造函数(若 Type 有默认构造函数)。

对应的 delete[] 会:

  1. 为数组每个元素调用析构函数
  2. 调用 operator delete[] 释放内存(可重载)。
关键注意:
  • 配对使用new[] 必须与 delete[] 配对,否则可能漏调析构函数(如用 delete 代替 delete[],仅调用第一个元素的析构);
  • 旧编译器兼容operator new[] 支持较晚,旧编译器可能 fallback 到全局 operator new,导致数组内存分配难以定制。

总结:概念地图

概念 角色 核心行为 可定制性
new operator 语言操作符 分配内存(调用 operator new) + 构造对象(placement new 隐式调用) 不可直接定制
operator new 内存分配函数 仅分配内存,返回 void* 可重载(全局/类)
placement new operator new 的重载版本 在指定内存地址上构造对象(通过额外参数指定地址) 可自定义重载
delete operator 语言操作符 析构对象 + 释放内存(调用 operator delete) 不可直接定制
operator delete 内存释放函数 仅释放内存 可重载(全局/类)

实践建议

  • 普通堆对象:直接用 new/delete(new operator + delete operator);
  • 定制内存分配:重载 operator new/operator delete
  • 指定内存构造:用 placement new,配合手动析构和内存释放;
  • 数组:严格配对 new[]delete[],避免漏调析构。

理解这些概念后,你就能灵活应对复杂内存管理场景(如内存池、对象池),精准控制对象的生命周期与内存分配!

你可能感兴趣的:(c++,java,开发语言)