結論導向:在現代 C++ 中,new/delete 是內存+對象生命週期的底層開關:new=“分配內存+構造對象”,delete=“析構對象+釋放內存”。面向工程治理,建議能不用就不用,優先 <span style="color:red">RAII</span>(如 std::unique_ptr/std::shared_ptr);但在需要顯式控制內存、池化分配、跨邊界 ABI 約束時,仍需精準掌握 new/delete。🚀
使用原則(務實版)
- 語義匹配:<span style="color:red">
new↔delete,new[]↔delete[]</span>,混用=未定義行為。 delete nullptr安全;重複 delete 是 <span style="color:red">未定義行為</span>。- 構造拋異常:分配的內存由運行時回收,但已構造的子對象仍需按規則析構(異常安全要到位)。
- 需要特定對齊或自定義池化時,使用對齊
new、類內 operator new 或 placement new。🧩
原理對比表(支持 vditor/Markdown)
| 形式 | 作用 | 失敗行為 | 典型場景 | 風險點 |
|---|---|---|---|---|
T* p = new T(args); |
分配 + 構造 | 拋出 std::bad_alloc |
單對象、細粒度控制 | 遺漏 delete ⇒ <span style="color:red">內存泄漏</span> |
T* a = new T[n]; |
分配數組 + 構造 n 次 | 拋出 | 大批量對象 | 需配對 delete[],否則 <span style="color:red">未定義行為</span> |
new(std::nothrow) T |
分配失敗返回 nullptr |
返回 nullptr |
硬實時/不拋異常路徑 | 忘判空 ⇒ <span style="color:red">空指針解引用</span> |
new (buf) T(placement) |
在給定緩衝區就地構造 | 不分配 | 內存池/共享內存/自定義區域 | 需顯式析構並釋放底層緩衝 |
new(std::align_val_t{A}) T |
指定對齊分配 | 拋出 | SIMD/硬件 DMA | delete 也需帶同樣對齊語義 |
1) 標準 new / delete(單對象)🙂
struct Conn {
explicit Conn(int fd) : fd_(fd) {/*校驗fd*/}
~Conn() {/*關閉句柄等清理*/}
private:
int fd_;
};
int main() {
Conn* c = new Conn(3); // 分配+構造
// ... 使用 c
delete c; // 析構+釋放
}
解釋:
new Conn(3)完成兩步:分配原始內存並調用構造函數。delete c先調用析構,再釋放內存;確保資源(文件句柄等)在析構中<span style="color:red">可審計地釋放</span>。- 工程落地:將資源釋放固化在析構中,消滅“忘記釋放”的偶發性。
2) 數組 new[] / delete[](批量對象)📦
struct Node { Node() {/*...*/} ~Node() {/*...*/} };
int main() {
Node* arr = new Node[1000]; // 構造1000次
// ... 批量處理
delete[] arr; // 析構1000次 + 釋放
}
解釋:
- 編譯器會記錄數組規模(或用實現策略保證可逐個析構)。
- 必須用
delete[]成對回收,否則會造成<span style="color:red">未定義行為</span>或漏析構。 - 當數組很大且構造昂貴時,可考慮對象池或
std::pmr以降低分配碎片。
3) std::nothrow(不拋異常路徑)🛡️
#include <new>
int* buf = new(std::nothrow) int[1'000'0000];
if (!buf) {
// 失敗分支:無異常,走降級策略
} else {
// ... 使用
delete[] buf;
}
解釋:
- 分配失敗時返回
nullptr,不拋異常,利於硬實時或低抖動路徑。 - 成本是必須<span style="color:red">顯式判空</span>;統一異常策略時慎用,避免雙路徑複雜度膨脹。
4) placement new(就地構造:池化/共享內存)⚙️
#include <new>
#include <memory> // C++20 起可用 construct_at/destroy_at
alignas(64) unsigned char storage[sizeof(Conn)]; // 預留對齊緩衝
Conn* c = ::new (storage) Conn(3); // 在 storage 上構造
// ... 使用 c
std::destroy_at(c); // 或顯式調用 c->~Conn()
/* storage 來自棧區,無需 delete;若來自 malloc/池,則按其來源釋放 */
解釋:
::new (storage) Conn(3)不分配內存,只在給定地址調用構造。- 生命週期由你管理:用
std::destroy_at(或顯式析構)銷燬對象。 - 若底層緩衝來自自定義池/
malloc,銷燬對象後再按來源釋放緩衝;勿對 placement 對象調用普通delete。 - 適合無 GC 場景的高性能業務,如網絡會話、對象複用。
5) 對齊 new(C++17+)與類內自定義分配 ♟️
#include <new>
struct alignas(64) Packet { /* ... */ };
Packet* p = ::new (std::align_val_t{64}) Packet();
// ... 使用
::operator delete(p, std::align_val_t{64}); // 對齊 delete(與 new 語義匹配)
解釋:
- 為 SIMD/Cache 友好或硬件 DMA 需求提供<span style="color:red">對齊保證</span>。
- 注意:釋放時也需攜帶相同對齊標籤,保持語義對稱。
- 大型系統可在類內自定義
operator new/delete實現對象池,降低分配抖動與碎片。
風險與治理清單(給工程經理的硬約束)✅
- 建立代碼規範:明確何處允許
new/delete,其餘使用 <span style="color:red">RAII</span> 容器與智能指針。 - 接口邊界返回擁有語義:
std::unique_ptr<T>;跨模塊共享用std::shared_ptr<T>,並評估<span style="color:red">循環引用</span>風險(用weak_ptr斷環)。 - 異常策略統一:要麼全拋,要麼全不拋;使用
std::nothrow時,強制判空。 - 性能敏感點:考慮 placement new + 對象池 或
std::pmr,降低分配次數與碎片。⚡
一圖速覽(vditor/Markdown)
| 關鍵點 | 做法 | 影響 |
|---|---|---|
| 生命週期管理 | <span style="color:red">RAII</span> 優先,必要時顯式 `new/delete` | 降低泄漏概率,收口可觀察性 |
| 語義匹配 | `new`↔`delete`;`new[]`↔`delete[]` | 違背即 <span style="color:red">未定義行為</span> |
| 失敗策略 | 異常 or `std::nothrow`(判空) | 一致性與可維護性 |
| 性能與對齊 | placement new / 對齊 new / 自定義池 | 延遲、抖動與緩存友好 |
| 跨邊界 | 智能指針/工廠函數返回所有權 | 降耦合、穩 ABI |
一句話回顧:把 new/delete 當作底層控制閥,在需要時精確使用;把資源生命週期收口到 <span style="color:red">RAII</span>,用制度與語義防止內存泄漏與未定義行為。🌟