現代 C++ 強烈建議使用 std::make_unique(C++14)和 std::make_shared(C++11)創建智能指針,而非手動 new 再包裝。本條款從代碼簡潔性、異常安全、性能優化三個維度論證優勢,並闡述不適用場景與替代方案。
// 使用 new:類型 Widget 出現兩次 std::unique_ptr<Widget> upw1(new Widget); std::shared_ptr<Widget> spw1(new Widget); // 使用 make:類型僅出現一次 auto upw2 = std::make_unique<Widget>(); auto spw2 = std::make_shared<Widget>();
減少重複降低維護成本、編譯時間與目標代碼膨脹。
考慮函數調用 processWidget(std::shared_ptr<Widget>, computePriority()),參數求值順序未定序。若先 new Widget 再 computePriority() 拋異常,裸指針洩漏。make_shared 將分配與構造融合為單一表達式,無中間裸指針狀態,天然異常安全。
// 危險:可能洩漏 processWidget(std::shared_ptr<Widget>(new Widget), computePriority()); // 安全:make_shared 保證無洩漏 processWidget(std::make_shared<Widget>(), computePriority());
make_unique 同理。
make_shared 專屬)make_shared 將對象與控制塊放在同一塊內存,僅需一次堆分配;new + shared_ptr 需兩次(對象 + 控制塊)。std::allocate_shared 同享此優勢。
| 場景 | 原因 | 替代方案 |
|---|---|---|
| 需要自定義刪除器 | make 函數不支持指定刪除器 | 直接 new 構造智能指針:std::shared_ptr<Widget> spw(new Widget, deleter); |
| 需要大括號初始化列表構造 | 完美轉發不支持大括號列表(見條款 30) | 先用 auto init = {1,2}; 推導為 initializer_list 再傳遞,或直接 new |
類自定義 operator new/delete |
make_shared 分配塊包含控制塊,大小不等於 sizeof(T),破壞自定義分配器假設 |
直接 new 或 allocate_shared 配合兼容分配器 |
| 大對象 + 長壽 weak_ptr | 控制塊與對象同塊內存,weak_ptr 存活導致大對象內存延遲釋放 | 直接 new,對象與控制塊分離分配 |
將 new 與智能指針構造放在單獨語句,避免編譯器在兩者間插入其他求值:
// 需自定義刪除器 std::shared_ptr<Widget> spw(new Widget, customDeleter); // 單一語句 processWidget(std::move(spw), computePriority()); // 移動語義避免拷貝引用計數開銷
new 與構造原子性,異常時刪除器會被調用。std::move 傳遞右值,避免 shared_ptr 拷貝的原子增減開銷。標準庫 C++14 才提供,C++11 可自行實現簡易版:
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// 注意:勿放入 std 命名空間,避免升級 C++14 時衝突
make_unique/make_shared 消除代碼重複、提升異常安全、make_shared 更有性能優勢。operator new/delete 的類、大對象配合長壽 weak_ptr。new,請在單獨語句中完成分配與構造,並用 std::move 傳遞右值以保持效率。