别再滥用 new/delete
很多 C++ 程序员都有一个共性:凡是要创建对象,手就不自觉地写上 new
;对象不用了,紧接着 delete
。 看起来“手动掌控内存”挺酷,但其实在现代 C++ 里,这种做法往往是性能问题和内存碎片的源头。
我在看别人代码时,经常能看到这样的片段👇
auto* p = new Foo();
// ...
delete p;
如果这些操作只偶尔出现,问题不大。但当它们出现在循环、频繁调用的逻辑里,就离“性能雪崩”不远了。
一、频繁 new/delete 的真正代价
new
和 delete
并不只是简单地“分配”和“释放内存”那么直接。 在背后,它们通常要调用操作系统的堆分配接口,比如 malloc/free
或平台特定的分配器。 这意味着:
每次分配都可能触发系统调用;
系统堆管理需要锁来保证线程安全;
长时间频繁分配、释放不同大小的内存块,会导致堆碎片化(heap fragmentation)。
碎片化是什么? 简单说,就是你虽然还有很多内存,但这些内存被零零碎碎地切得太散,导致大块连续空间不够用了。
这会直接带来两个问题:
性能下降:分配器需要更久才能找到合适的空闲块;
内存占用虚高:进程总内存飙升,但有效利用率不高。
所以,当你在游戏循环、网络请求或实时系统里反复 new/delete
,每一帧都可能在悄悄挖坑。
二、RAII 并不是可选项
很多人以为“我用完 delete 掉了,不就没泄漏了吗?” 但内存泄漏不是唯一问题。C++ 的真正哲学是:资源的生命周期应当由对象语义自动管理。
最简单的反例:
void foo() {auto* data = new int[100];doSomething();throw std::runtime_error("oops");delete[] data;
}
异常一抛,delete[]
根本执行不到,内存就泄漏了。
所以 RAII(Resource Acquisition Is Initialization)是 C++ 程序员的基本功。 要想减少手动管理,就要学会用智能指针:
#include <memory>void foo() {auto data = std::make_unique<int[]>(100);doSomething();// 自动释放,无需 delete
}
这不仅减少泄漏风险,也能让代码逻辑更清晰: 你不再关心“何时释放”,而是专注于“这个资源是谁的”。
三、频繁创建销毁对象?该用对象池了
如果你的场景确实需要反复创建和释放对象,比如:
游戏中大量的子弹、粒子;
网络服务中频繁的请求上下文;
高频消息处理系统。
那么最好的办法就是使用 对象池(Object Pool)。
一个简单的对象池示例:
template <typename T>
class ObjectPool {
public:T* acquire() {if (!pool.empty()) {T* obj = pool.back();pool.pop_back();return obj;}returnnew T();}void release(T* obj) {pool.push_back(obj);}~ObjectPool() {for (auto* obj : pool)delete obj;}private:std::vector<T*> pool;
};
使用示例:
ObjectPool<Foo> pool;auto* f1 = pool.acquire();
auto* f2 = pool.acquire();// 使用完后归还
pool.release(f1);
pool.release(f2);
这样做的好处是:
减少频繁的堆分配;
内存布局更稳定,缓存命中率更高;
生命周期管理更集中。
如果项目规模较大,可以考虑引入专业的内存池库,比如 Boost.Pool 或者 mimalloc。
四、现代 C++ 的内存分配器新思路
从 C++17 开始,标准库引入了可插拔分配器机制。 这意味着你可以自定义 std::vector
、std::unordered_map
等容器的分配行为,而无需自己改容器源码。
#include <vector>
#include <memory_resource>void test() {std::pmr::monotonic_buffer_resource pool;std::pmr::vector<int> v(&pool);for (int i = 0; i < 1000; ++i)v.push_back(i);
}
std::pmr
(Polymorphic Memory Resource)系列让你可以在容器之间共享内存池,大幅减少小块分配的碎片问题。 很多游戏引擎和金融系统都已经用上了这套机制。
五、手动控制不是自由,而是负担
过去我们津津乐道“C++ 给你完全的控制权”,但现在看来,这句话得改一改:
真正的高手,不是频繁使用
new/delete
,而是尽量避免使用它们。
new/delete 只是工具,不该是默认选择。你需要考虑:
是否可以用栈对象代替?
是否能用智能指针自动管理?
是否应该引入内存池?
是否能用
std::pmr
提升局部分配性能?
性能优化从不是“玄学”,它往往隐藏在一些“理所当然”的习惯里。new/delete
没错,但不加节制的使用,就像在高速公路上打滑。