Stories

Detail Return Return

藍易雲:C++ new和delete的用法 - Stories Detail

結論導向:在現代 C++ 中,new/delete內存+對象生命週期的底層開關:new=“分配內存+構造對象”,delete=“析構對象+釋放內存”。面向工程治理,建議能不用就不用,優先 <span style="color:red">RAII</span>(如 std::unique_ptr/std::shared_ptr);但在需要顯式控制內存、池化分配、跨邊界 ABI 約束時,仍需精準掌握 new/delete。🚀


使用原則(務實版)

  • 語義匹配:<span style="color:red">newdeletenew[]delete[]</span>,混用=未定義行為
  • delete nullptr 安全;重複 delete 是 <span style="color:red">未定義行為</span>。
  • 構造拋異常:分配的內存由運行時回收,但已構造的子對象仍需按規則析構(異常安全要到位)。
  • 需要特定對齊或自定義池化時,使用對齊 new類內 operator newplacement 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>,用制度與語義防止內存泄漏未定義行為。🌟

user avatar lenglingx Avatar wenzhongdejianpan Avatar shu_5b5b4cde7027a Avatar wuxiedekeben Avatar renxingdebenma Avatar kumendezhenzhishan Avatar yuezhang_5e5e7da0beeea Avatar uname67 Avatar chat2db Avatar
Favorites 9 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.