C++ 现代编程实践:用 make_unique 告别裸 new/delete 1. 为什么应该抛弃裸 new/delete在 C11 之前动态内存管理几乎是开发者的日常一个new配上若干个delete还要小心异常安全和所有权问题。手动管理内存至少会带来三大痛点内存泄漏delete忘记调用、提前return或者抛出异常时资源就会丢失二次释放与野指针重复删除同一块内存或继续使用已释放的指针会导致未定义行为所有权混乱团队协作时很难从裸指针一眼看出“谁负责释放”容易写出 bug。自 C11 以来标准库提供了std::unique_ptr和std::make_uniqueC14 起让我们可以彻底告别显式new和delete。本文将介绍如何安全、高效地替换现有代码中的裸指针并解释背后的设计动机。2. make_unique 基本用法std::make_unique是创建std::unique_ptr的工厂函数它直接在堆上构造对象并返回一个独占所有权的智能指针。基本形式为auto p std::make_uniqueWidget(arg1, arg2);等价于原先的Widget* raw new Widget(arg1, arg2);但好处是不需要写delete离开作用域时自动释放异常安全即使构造函数抛出异常也不会泄漏先前分配的资源明确表达了“独占所有权”的语义避免了写两次类型名auto配合make_unique减少冗余。3. 替换模式从 new/delete 到 unique_ptr下面通过几个常见场景展示如何系统地将裸指针代码迁移到make_unique。3.1 普通堆对象// 旧代码手动 new/delete void useWidget() { Widget* w new Widget(100); // ... delete w; // 容易忘记还可能被异常跳过 } // 新代码make_unique void useWidget() { auto w std::make_uniqueWidget(100); // ... 不再需要 delete }3.2 类成员变量class Manager { // 旧裸指针 手动管理 Widget* w_; public: Manager() : w_(new Widget(42)) {} ~Manager() { delete w_; } // 还需要拷贝/移动操作……一团乱 }; class Manager { // 新unique_ptr 自动管理生命周期 std::unique_ptrWidget w_; public: Manager() : w_(std::make_uniqueWidget(42)) {} // 析构函数自动释放无需手写 // 移动语义天然支持禁止拷贝 };3.3 容器中的对象指针// 旧vector 中存裸指针销毁前必须逐个 delete std::vectorWidget* widgets; widgets.push_back(new Widget(1)); widgets.push_back(new Widget(2)); for (auto p : widgets) delete p; // 极易漏删 // 新存 unique_ptrclear/析构自动释放 std::vectorstd::unique_ptrWidget widgets; widgets.push_back(std::make_uniqueWidget(1)); widgets.push_back(std::make_uniqueWidget(2)); // 容器销毁时自动释放所有 Widget4. 为什么推荐 make_unique 而不是直接 new unique_ptr你当然可以这样写std::unique_ptrWidget p(new Widget(arg));但是仍然显式写了new违背了“禁止显式 new/delete”的原则。更重要的是make_unique提供了额外的异常安全保证和优化可能某些实现可以减少一次内存分配。此外工厂函数风格使得代码意图更清晰、更容易审计。5. 注意事项与边界场景C11 兼容std::make_unique是 C14 新增的如果还停留在 C11可以自己实现一个简单的版本。自定义删除器unique_ptr支持自定义删除器但make_unique无法传递删除器此时仍需使用new构造unique_ptr。不过现代代码中这类场景很少且通常可以用shared_ptr或封装类替代。数组类型std::make_unique也支持数组例如std::make_uniquechar[](1024)。不过要注意unique_ptrT[]的删除器与标量不同不要混淆。所有权转移需要传递所有权时使用std::move转移unique_ptr本身而不是转移裸指针。与共享所有权的区别当确实需要共享所有权时再考虑std::make_shared。不要因为懒得设计所有权就用shared_ptr一把梭。6. 在团队中推行“禁止 new/delete”可以借助静态分析工具如 clang-tidy 的modernize-make-unique检查项或编译器警告来强制执行这一规则。团队规范中明确写清楚绝对不允许出现裸new和delete所有动态分配都通过std::make_unique或std::make_shared完成裸指针仅用于非拥有性引用且需要标明“不负责释放”。这样不仅能大幅减少内存相关 bug也使得代码更容易被新成员理解和维护。7. 总结用std::make_unique替代裸new/delete是现代 C 的最佳实践之一。它消除了手动资源管理的麻烦提升了异常安全性并且让所有权语义一目了然。本文提供的替换模式和注意事项可以帮助你平滑迁移现有代码并建立一套可靠的编码规范。从今天起让我们对显式new和delete说“不”。