在C++里,我们平时写的new表达式会做两件事:从堆上分配内存,然后调用构造函数初始化对象。但placement new只做其中一件事,它把对象构造在调用者指定的内存地址上,本身不申请任何内存。这种机制在需要精准控制对象存储位置的场景中非常有用。

placement new的基本语法
placement new是C++标准库提供的一种重载形式的new。它接收一个指针参数,表示对象应该被构造在哪里。使用时需要包含头文件<new>。
#include <new>
#include <iostream>
struct Point {
int x;
int y;
Point(int x_, int y_) : x(x_), y(y_) {}
void print() const {
std::cout << "x=" << x << ", y=" << y << std::endl;
}
};
int main() {
// 已分配好的一块内存,可以是栈、堆或共享内存
alignas(Point) char buffer[sizeof(Point)];
// 在buffer地址上原地构造Point对象
Point* p = new (buffer) Point(10, 20);
p->print();
// 必须手动调用析构函数
p->~Point();
return 0;
}
为什么需要手动调用析构函数
因为placement new没有分配内存,所以不能用delete来释放。delete会尝试释放内存,但buffer可能是栈上的数组或者内存池里的块,直接delete会导致未定义行为。正确做法是显式调用对象的析构函数,内存的回收由原本的管理方式负责。
典型使用场景
- 内存池:一次性申请大块内存,后续用placement new在池中构造对象,减少系统调用。
- 共享内存:多个进程映射同一块物理内存,需要在固定地址构造可读写对象。
- 自定义容器:如std::vector在扩容时先用分配器拿内存,再用placement new构造元素。
使用时的注意事项
内存对齐
用来放置对象的内存必须满足该类型的对齐要求。上面例子中用了alignas(Point)来保证char数组对齐,否则可能出现总线错误。
异常安全
如果placement new构造时构造函数抛异常,内存不会被释放(因为本来就不是它分配的),但对象没有构造成功,调用者需要保证后续逻辑不会误用这块内存。
| 对比项 | 普通new | placement new |
|---|---|---|
| 是否分配内存 | 是 | 否 |
| 释放方式 | delete | 手动调析构+原内存管理 |
| 典型用途 | 常规对象创建 | 内存池、共享内存 |
记住:placement new只是构造,不负责内存生命周期,配对的手工析构和内存回收才是完整用法。
结合内存池的简单示例
#include <new>
#include <cstddef>
class Pool {
char* mem;
std::size_t offset;
std::size_t capacity;
public:
Pool(std::size_t size) : offset(0), capacity(size) {
mem = new char[size];
}
~Pool() { delete[] mem; }
template<typename T, typename... Args>
T* construct(Args&&... args) {
void* addr = mem + offset;
offset += sizeof(T);
return new (addr) T(std::forward<Args>(args)...);
}
};
上面这个Pool类在构造时申请一整块内存,construct方法通过placement new在内部地址上创建对象。使用完后Pool析构时统一释放内存,期间每个对象都应视情况手动调用析构。
掌握placement new能让你在底层C++开发中更自如地控制对象和内存的关系,但也要小心对齐、析构和异常这些隐藏的坑。
placement_newC++原地构造修改时间:2026-07-30 00:57:20