C++ 的 newdelete 是动态内存核心,但现代代码中应避免裸用。理解其机制避免陷阱:new 分配内存+构造对象,delete 析构+释放内存。

Widget 示例类(全局唯一定义)

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
// new 表达式(推荐日常)
Widget* p1 = new Widget(42); // operator new + 构造
delete p1; // 析构 + operator delete

// operator new(特殊用,如 placement new)
void* raw = operator new(sizeof(Widget));
Widget* p2 = new (raw) Widget(42); // placement new 构造
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]();  // 值初始化为0
delete[] arr; // 必须配对 new[] + delete[]

铁律: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(); // 无 delete

new/delete vs malloc/free

特性new/deletemalloc/free
类型安全
构造/析构
失败bad_allocnullptr
重载

优先 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

陷阱 & 最佳实践

  1. 泄漏:忘 delete。:智能指针。
  2. 重复 delete / 野指针:设 nullptr 后 delete。
  3. 悬挂指针:delete 后别用。
  4. 异常安全:构造函数抛异常,内存泄漏。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;