目录
[一、定位 new 表达式(Placement New)](#一、定位 new 表达式(Placement New))
[1.1 什么是定位 new?](#1.1 什么是定位 new?)
[1.2 语法格式](#1.2 语法格式)
[1.3 为什么需要定位 new?](#1.3 为什么需要定位 new?)
[1.4 代码示例:基于定位 new 的内存池](#1.4 代码示例:基于定位 new 的内存池)
[1.5 注意事项](#1.5 注意事项)
[2.1 为什么需要类特定的new/delete?](#2.1 为什么需要类特定的new/delete?)
[2.2 语法规则](#2.2 语法规则)
[2.3 代码示例:带内存池和分配统计的类](#2.3 代码示例:带内存池和分配统计的类)
[三、定位 new 与类特定new的协同使用](#三、定位 new 与类特定new的协同使用)
[3.1 场景需求](#3.1 场景需求)
[3.2 代码示例:延迟构造的对象](#3.2 代码示例:延迟构造的对象)
[4.1 定位 new 的常见误区](#4.1 定位 new 的常见误区)
[4.2 类特定new/delete的设计原则](#4.2 类特定new/delete的设计原则)
[4.3 性能优化建议](#4.3 性能优化建议)
在 C++ 中,内存管理是性能优化和资源控制的核心。除了标准的new
/delete
操作符外,C++ 还提供了两种高级机制:定位 new 表达式(Placement New)和 类特定的new
/delete
表达式(Class-specific new/delete)。
一、定位 new 表达式(Placement New)
1.1 什么是定位 new?
定位 new(Placement New)是 C++ 的一种特殊语法,允许在 已分配的原始内存(Raw Memory) 中直接构造对象。与普通new
不同,它不负责内存分配,仅执行对象的构造过程。
核心特点:
- 不分配内存:需要用户提前提供已分配的内存地址。
- 直接调用构造函数:在指定内存地址上构造对象。
- 头文件依赖 :需要包含
<new>
头文件。
1.2 语法格式
定位 new 的语法如下:
cpp
#include <new> // 必须包含此头文件
// 在已分配的内存p上构造类型T的对象
T* obj = new (p) T(构造参数);
其中:
p
是指向已分配内存的指针(通常是void*
类型)。T
是要构造的对象类型。构造参数
是传递给T
构造函数的参数(可选)。
1.3 为什么需要定位 new?
普通new
的执行流程包含两步:
- 调用
operator new
分配内存(可能抛出std::bad_alloc
异常)。 - 调用对象的构造函数。
而定位 new 跳过了第一步,直接在已有内存上构造对象。其典型应用场景包括:
- 内存池(Memory Pool) :预先分配大块内存,重复利用内存块构造对象(避免频繁调用
malloc
/free
)。- 嵌入式系统:在固定物理地址(如硬件寄存器映射地址)创建对象。
- 自定义内存管理 :配合
operator new
实现更灵活的内存分配策略。
1.4 代码示例:基于定位 new 的内存池
场景描述: 假设我们需要频繁创建和销毁大量小对象(如游戏中的粒子),使用普通new
会导致内存碎片和性能下降。通过内存池预先分配连续内存块,再用定位 new 构造对象,可以显著提升效率。
实现代码:
cpp
#include <iostream>
#include <new>
#include <vector>
// 内存池类(简化版)
class MemoryPool {
private:
char* pool; // 内存池起始地址
size_t block_size; // 每个内存块的大小
size_t block_num; // 内存块数量
bool* used; // 记录内存块是否被使用
public:
// 构造函数:初始化内存池
MemoryPool(size_t block_size, size_t block_num)
: block_size(block_size), block_num(block_num) {
// 分配连续内存(对齐处理:简化示例,实际需考虑对齐)
pool = new char[block_size * block_num];
used = new bool[block_num]{false}; // 初始化为未使用
}
// 分配一个内存块(返回原始内存地址)
void* allocate() {
for (size_t i = 0; i < block_num; ++i) {
if (!used[i]) {
used[i] = true;
return pool + i * block_size;
}
}
return nullptr; // 内存池已满
}
// 释放一个内存块
void deallocate(void* p) {
if (p < pool || p >= pool + block_size * block_num) return;
size_t index = (static_cast<char*>(p) - pool) / block_size;
used[index] = false;
}
// 析构函数:释放内存池
~MemoryPool() {
delete[] pool;
delete[] used;
}
};
// 测试类:需要频繁创建的对象
class Particle {
private:
int id;
public:
Particle(int id) : id(id) {
std::cout << "Particle " << id << " 构造完成" << std::endl;
}
~Particle() {
std::cout << "Particle " << id << " 析构完成" << std::endl;
}
void print() const {
std::cout << "Particle " << id << " 正在运行" << std::endl;
}
};
int main() {
// 初始化内存池:每个块64字节(足够容纳Particle),共10个块
MemoryPool pool(64, 10);
// 使用定位new构造对象
std::vector<Particle*> particles;
for (int i = 0; i < 5; ++i) {
void* mem = pool.allocate();
if (!mem) {
std::cerr << "内存池不足!" << std::endl;
break;
}
// 在内存池的内存上构造Particle对象
Particle* p = new (mem) Particle(i);
particles.push_back(p);
}
// 调用对象方法
for (auto p : particles) {
p->print();
}
// 显式调用析构函数(定位new不会自动调用析构)
for (auto p : particles) {
p->~Particle(); // 必须手动调用析构!
pool.deallocate(p); // 释放内存块回内存池
}
return 0;
}
内存池类MemoryPool
:
- 预先分配连续内存(
pool
),并通过used
数组标记每个内存块的使用状态。 allocate()
方法查找空闲内存块并返回其地址。deallocate()
方法将内存块标记为空闲。
测试类Particle
:构造函数和析构函数打印日志,方便观察生命周期。
主函数:
- 通过内存池分配原始内存,使用定位 new 构造
Particle
对象。 - 手动调用析构函数(
~Particle()
)并释放内存块回内存池(否则会导致内存泄漏)。
运行结果:

1.5 注意事项
- 手动调用析构函数 :定位 new 构造的对象不会自动调用析构函数 ,必须手动调用
obj->~T()
释放资源(如关闭文件、释放堆内存等)。 - 内存对齐 :如果对象需要对齐(如
alignas(16)
),内存池分配的内存必须满足对齐要求(可通过std::align
或自定义对齐分配实现)。 - 异常处理:如果构造函数抛出异常,定位 new 会自动回滚(不影响已分配的内存)。
二、类特定的new
/delete
表达式
2.1 为什么需要类特定的new
/delete
?
默认情况下,new
/delete
使用全局的operator new
/operator delete
分配内存,这可能无法满足某些场景的需求:
- 性能优化:高频创建的类需要更高效的内存分配(如内存池)。
- 内存对齐:某些硬件或算法需要特定对齐的内存(如 SIMD 指令要求 128 位对齐)。
- 调试与监控:需要记录类的内存分配次数、大小,或检测内存泄漏。
通过重载类的operator new
和operator delete
,可以为特定类定制内存分配逻辑。
2.2 语法规则
类特定的operator new
和operator delete
是类的静态成员函数(无需实例化即可调用),其语法如下:
cpp
class MyClass {
public:
// 普通版本:分配单个对象
static void* operator new(size_t size);
static void operator delete(void* p, size_t size);
// 数组版本:分配对象数组
static void* operator new[](size_t size);
static void operator delete[](void* p, size_t size);
// 可选:带额外参数的版本(如对齐)
static void* operator new(size_t size, std::align_val_t align);
static void operator delete(void* p, std::align_val_t align);
};
size
参数 :由编译器自动传递,代表需要分配的内存大小(对于普通对象是sizeof(MyClass)
,数组是sizeof(MyClass)*N + 额外开销
)。align_val_t
参数 :C++17 引入,用于指定对齐要求(当类使用alignas
修饰时自动传递)。- 必须与全局
operator new
的行为兼容(如分配失败时抛出std::bad_alloc
异常)。
2.3 代码示例:带内存池和分配统计的类
场景描述
设计一个Widget
类,要求:
-
所有实例通过内存池分配,减少内存碎片。
-
统计类的总分配次数和总分配内存大小。
-
支持自定义对齐(如 32 字节对齐)。
实现代码
cpp
#include <iostream>
#include <new>
#include <vector>
#include <cstdalign> // 对齐相关头文件
// 全局统计信息
struct AllocStats {
size_t count = 0; // 分配次数
size_t total_bytes = 0; // 总分配字节数
};
// 内存池(支持对齐)
class AlignedMemoryPool {
private:
char* pool;
size_t block_size;
size_t block_num;
bool* used;
std::align_val_t align; // 对齐要求
public:
AlignedMemoryPool(size_t block_size, size_t block_num, std::align_val_t align)
: block_size(block_size), block_num(block_num), align(align) {
// 分配对齐内存(使用std::aligned_alloc)
pool = static_cast<char*>(std::aligned_alloc(static_cast<size_t>(align), block_size * block_num));
used = new bool[block_num]{false};
}
void* allocate() {
for (size_t i = 0; i < block_num; ++i) {
if (!used[i]) {
used[i] = true;
return pool + i * block_size;
}
}
return nullptr;
}
void deallocate(void* p) {
if (p < pool || p >= pool + block_size * block_num) return;
size_t index = (static_cast<char*>(p) - pool) / block_size;
used[index] = false;
}
~AlignedMemoryPool() {
std::free(pool); // 对齐内存用std::free释放
delete[] used;
}
};
// 带内存池和统计的Widget类
class Widget {
private:
int data;
static AllocStats stats; // 分配统计
static AlignedMemoryPool pool; // 类共享的内存池
public:
// 构造函数
Widget(int data) : data(data) {}
// 析构函数
~Widget() {}
// 重载operator new(普通版本)
static void* operator new(size_t size) {
void* mem = pool.allocate();
if (!mem) {
throw std::bad_alloc(); // 分配失败抛出异常
}
// 更新统计信息
stats.count++;
stats.total_bytes += size;
return mem;
}
// 重载operator delete(普通版本)
static void operator delete(void* p, size_t size) {
pool.deallocate(p);
// 更新统计信息
stats.count--;
stats.total_bytes -= size;
}
// 重载operator new(对齐版本,C++17+)
static void* operator new(size_t size, std::align_val_t align) {
// 这里简化处理:假设内存池已按align对齐
void* mem = pool.allocate();
if (!mem) {
throw std::bad_alloc();
}
stats.count++;
stats.total_bytes += size;
return mem;
}
// 重载operator delete(对齐版本)
static void operator delete(void* p, std::align_val_t align) {
pool.deallocate(p);
stats.count--;
stats.total_bytes -= align; // 简化处理,实际应使用size参数
}
// 打印统计信息
static void printStats() {
std::cout << "Widget分配统计:" << std::endl
<< " 总分配次数: " << stats.count << std::endl
<< " 总分配字节数: " << stats.total_bytes << std::endl;
}
};
// 初始化静态成员
AllocStats Widget::stats;
AlignedMemoryPool Widget::pool(
sizeof(Widget) + alignof(Widget), // 内存块大小(考虑对齐填充)
10, // 10个内存块
std::align_val_t(32) // 32字节对齐
);
int main() {
std::vector<Widget*> widgets;
// 创建5个Widget对象
for (int i = 0; i < 5; ++i) {
try {
Widget* w = new Widget(i);
widgets.push_back(w);
} catch (const std::bad_alloc& e) {
std::cerr << "分配失败: " << e.what() << std::endl;
break;
}
}
Widget::printStats(); // 打印分配统计
// 释放对象
for (auto w : widgets) {
delete w;
}
Widget::printStats(); // 再次打印(应恢复为0)
return 0;
}
①全局统计结构AllocStats
:记录类的分配次数和总内存大小。
②对齐内存池AlignedMemoryPool
:使用std::aligned_alloc
分配对齐内存(支持 32 字节对齐)。
③Widget
类的重载方法:
-
operator new
从内存池分配内存,并更新统计信息。 -
operator delete
将内存归还内存池,并更新统计信息。 -
支持对齐版本(C++17),满足需要高对齐的场景(如 SIMD 指令)。
运行结果:
cpp
Widget分配统计:
总分配次数: 5
总分配字节数: 400 // 假设每个Widget占80字节(含对齐填充),5*80=400
Widget分配统计:
总分配次数: 0
总分配字节数: 0
三、定位 new 与类特定new
的协同使用
3.1 场景需求
在某些情况下,需要结合类特定new
和定位 new:
- 类特定
new
负责分配内存(可能来自内存池)。 - 定位 new 在已分配的内存上构造对象(如延迟构造)。
3.2 代码示例:延迟构造的对象
cpp
#include <iostream>
#include <new>
class HeavyObject {
private:
int* data; // 大数组模拟"重"资源
public:
HeavyObject() {
data = new int[1000000]; // 模拟大量内存分配
std::cout << "HeavyObject 构造完成" << std::endl;
}
~HeavyObject() {
delete[] data;
std::cout << "HeavyObject 析构完成" << std::endl;
}
void work() const {
std::cout << "HeavyObject 工作中..." << std::endl;
}
// 类特定new:普通版本(分配内存)
static void* operator new(size_t size) {
std::cout << "类特定new:分配 " << size << " 字节" << std::endl;
return ::operator new(size); // 调用全局new分配内存
}
// 类特定delete:普通版本(释放内存)
static void operator delete(void* p, size_t size) {
std::cout << "类特定delete:释放 " << size << " 字节" << std::endl;
::operator delete(p); // 调用全局delete释放内存
}
// 定位new专用重载:接受void*参数
static void* operator new(size_t size, void* ptr) {
std::cout << "定位new:在地址 " << ptr << " 构造对象" << std::endl;
return ptr; // 直接返回传入的内存地址
}
// 定位new配套的delete(用于构造失败时的回滚)
static void operator delete(void* ptr, void*) noexcept {
std::cout << "定位delete:无需释放内存(由用户管理)" << std::endl;
// 定位new的delete不执行实际释放,因为内存由用户管理
}
};
int main() {
// 1. 分配原始内存(调用类特定new)
void* mem = HeavyObject::operator new(sizeof(HeavyObject));
// 2. 使用定位new构造对象
HeavyObject* obj = new (mem) HeavyObject();
// 3. 使用对象
obj->work();
// 4. 显式调用析构函数(定位new需要手动析构)
obj->~HeavyObject();
// 5. 释放原始内存(调用类特定delete)
HeavyObject::operator delete(mem, sizeof(HeavyObject));
return 0;
}
关键流程:
- 分配内存 :通过类特定
operator new
分配原始内存(不构造对象)。 - 构造对象:使用定位 new 在原始内存上调用构造函数。
- 析构对象:手动调用析构函数释放资源。
- 释放内存 :通过类特定
operator delete
释放原始内存。
运行结果:

四、常见问题与最佳实践
4.1 定位 new 的常见误区
- 忘记调用析构函数:定位 new 构造的对象不会自动析构,若对象持有资源(如堆内存、文件句柄),会导致资源泄漏。
- 重复构造:在同一块内存上多次调用定位 new(覆盖未析构的对象)会导致未定义行为。
4.2 类特定new
/delete
的设计原则
- 与全局行为兼容 :分配失败时应抛出
std::bad_alloc
,释放空指针应安全(不执行操作)。 - 避免内存泄漏 :确保
operator delete
正确释放operator new
分配的内存(尤其是自定义内存池时)。 - 对齐处理 :若类需要对齐(如
alignas(16)
),需重载对齐版本的operator new
/delete
。
4.3 性能优化建议
- 内存池 :高频创建的小对象应使用内存池(减少
malloc
调用次数)。 - 缓存友好 :通过类特定
new
让相关对象分配到连续内存(提升 CPU 缓存命中率)。 - 避免碎片 :数组对象使用
operator new[]
分配连续内存(而非多个operator new
)。
五、总结
技术 | 核心功能 | 典型场景 | 关键注意事项 |
---|---|---|---|
定位 new | 在已分配内存上构造对象 | 内存池、固定地址对象、延迟构造 | 手动调用析构函数 |
类特定new /delete |
自定义类的内存分配逻辑 | 性能优化、内存对齐、分配统计 | 静态成员、异常安全、对齐支持 |
通过灵活运用定位 new 和类特定new
/delete
,可以显著提升 C++ 程序的内存管理效率,满足高性能、低延迟或特定硬件的需求。在实际项目中,建议结合内存池、对齐分配等技术,根据具体场景选择合适的内存管理策略。