C++ 的 new 和 delete 是动态内存核心,但现代代码中应避免裸用。理解其机制避免陷阱:new 分配内存+构造对象,delete 析构+释放内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <print> #include <format>
class Widget { public: Widget(int value) : value_(value) { std::println("Widget constructed with value: {}", value_); } ~Widget() { std::println("Widget destroyed"); } private: int value_; };
|
new 表达式 vs operator new 函数
new 表达式(语言操作符,不可重载):分配内存(调用 operator new)+构造对象,失败抛 std::bad_alloc。
operator new(可重载函数):仅分配内存,返回 void*,类似 malloc。
1 2 3 4 5 6 7 8 9
| Widget* p1 = new Widget(42); delete p1;
void* raw = operator new(sizeof(Widget)); Widget* p2 = new (raw) Widget(42); p2->~Widget(); operator delete(raw);
|
| 特性 | new 表达式 | operator new |
|---|
| 类型 | 操作符 | 函数 |
| 功能 | 分配+构造 | 仅分配 |
| 返回 | T* | void* |
| 重载 | 否 | 是 |
为什么区分:自定义分配(内存池)、placement new、调试拦截。
示例自定义:
1 2 3 4 5 6 7
| void* operator new(std::size_t size) { std::println("Allocating {} bytes", size); void* p = std::malloc(size); if (!p) throw std::bad_alloc{}; return p; } void operator delete(void* p) noexcept { std::free(p); }
|
变体
数组
1 2
| int* arr = new int[10](); delete[] arr;
|
铁律:new[] 配 delete[],new 配 delete。混用 UB。
nothrow
1 2 3
| #include <new> int* p = new (std::nothrow) int[1'000'000'000]; if (!p) std::println("Failed");
|
Placement new
1 2 3
| alignas(Widget) unsigned char buf[sizeof(Widget)]; Widget* w = new (buf) Widget(42); w->~Widget();
|
new/delete vs malloc/free
| 特性 | new/delete | malloc/free |
|---|
| 类型安全 | 是 | 否 |
| 构造/析构 | 是 | 否 |
| 失败 | bad_alloc | nullptr |
| 重载 | 是 | 否 |
优先 new/delete,除非 C 接口。
失败处理
默认抛异常,可设 handler:
1 2 3 4 5 6 7
| #include <new> void handler() { std::println("OOM!"); std::set_new_handler(nullptr); throw std::bad_alloc{}; } int main() { std::set_new_handler(handler); }
|
或用 nothrow。
自定义(类/全局)
类成员重载示例:
1 2 3 4 5 6 7 8 9
| class MyClass { public: void* operator new(std::size_t size) { return ::operator new(size); } void operator delete(void* p) noexcept { ::operator delete(p); } };
|
C++17 对齐:用 std::align_val_t。
陷阱 & 最佳实践
- 泄漏:忘 delete。解:智能指针。
- 重复 delete / 野指针:设
nullptr 后 delete。 - 悬挂指针:delete 后别用。
- 异常安全:构造函数抛异常,内存泄漏。解:
std::make_unique。
错误:
1 2 3
| Widget* p = new Widget(42); risky(); delete p;
|
正确:
1 2
| auto p = std::make_unique<Widget>(42); risky();
|
现代 C++(优先!)
智能指针(<memory>):
1 2
| auto u = std::make_unique<Widget>(42); auto s = std::make_shared<Widget>(42);
|
make_ 胜过 new:异常安全,一次分配。
容器:
1 2
| std::vector<Widget> vec; vec.emplace_back(42);
|
RAII:资源生命周期绑对象。
1 2 3 4 5 6 7 8 9
| class RAIIResource { void* res_; public: RAIIResource() : res_(acquire()) {} ~RAIIResource() { release(res_); } RAIIResource(const RAIIResource&) = delete; RAIIResource(RAIIResource&& o) noexcept : res_(o.res_) { o.res_ = nullptr; } };
|
性能 & 调试
- 内存池:高频小对象用,重载 operator new。
- 对齐:
alignas(64) 缓存友好。 - 工具:Valgrind (
--leak-check=full),ASan (-fsanitize=address)。
总结
- 配对:new/delete,new[]/delete[]。
- 避免裸 new:用 unique_ptr/shared_ptr/vector。
- RAII:自动管理。
- 自定义慎用:遵循匹配规则。
现代 C++:裸 new/delete 是例外。智能指针+容器=零泄漏、零手动。
1 2 3 4 5
| auto p = std::make_unique<Widget>(42);
Widget* p = new Widget(42); delete p;
|