C++make_unique创建独占智能指针

2026-04-11 07:15:25 1774阅读 0评论

make_unique:C++里那个“不声不响就把内存管得明明白白”的帮手

刚学智能指针那会儿,我总在newunique_ptr之间反复横跳:先new个对象,再用unique_ptr包一层——写起来像在厨房里切菜前还得先擦三遍刀。直到某天调试一个崩溃的构造函数调用,才意识到:手动new再包裹,不只是啰嗦,更是隐患的温床

make_unique不是语法糖,它是C++14给独占语义补上的最后一块逻辑拼图。

它最实在的作用,就一条:把对象构造和智能指针绑定在一次表达式里完成,杜绝中间状态泄露
比如这段看似无害的代码:

auto p = std::unique_ptr<Foo>(new Foo(1, "hello", std::string(1000, 'x')));

表面看没问题,但只要std::string(1000, 'x')抛异常(比如内存不足),new Foo(...)已经执行成功,而unique_ptr还没来得及接管——内存泄漏就这么发生了。更糟的是,这种问题在压力测试或低内存环境下才露头,日常根本测不出来。

而换成make_unique

auto p = std::make_unique<Foo>(1, "hello", std::string(1000, 'x'));

编译器保证:要么全部成功(对象构造完、指针接管完毕),要么全失败(任何一步异常,已分配资源自动释放)。这不是靠运气,是C++标准对make_unique的明确要求:它内部使用完美转发,并在异常安全边界内完成资源生命周期绑定。

有人问:“那make_unique<T>()unique_ptr<T>(new T())性能差多少?”
实测过,在开启优化(-O2)后,两者生成的汇编几乎一致——make_unique不额外增加开销,却稳稳堵住一个经典坑。它省的不是CPU周期,是调试凌晨三点的崩溃日志的时间。

另一个常被忽略的细节:make_unique天然支持数组形式,而且比裸new[]更干净:

auto arr = std::make_unique<int[]>(100);  // 创建含100个int的数组
// 不需要写 delete[],也不用记 size —— unique_ptr<int[]> 自带正确析构

注意,这里不能写make_unique<int[100]>()——那是类型错误。make_unique<T[]>只接受一个size参数,且T必须是非引用、非const原始类型(或满足类似要求的类)。这点和make_shared不同,但恰恰说明:make_unique的设计哲学是“直给”,不绕弯子,也不替你做假设

顺带一提,make_unique不支持自定义删除器(比如std::default_delete之外的)。如果真需要,比如封装文件句柄或GPU资源,那就老老实实写unique_ptr<T, CustomDeleter>,再用new——这不是倒退,而是清醒:当语义复杂到超出通用模式时,显式即清晰

实际项目中,我习惯把make_unique当成默认选项。只有两种情况我会破例:

  • 构造函数是explicit且参数类型易歧义(比如make_unique<std::string>(5, 'a')可能被误读为string(5,'a')而非string("5")),这时加个注释或拆成两步更稳妥;
  • 需要传递std::move过的右值引用,而目标类型移动构造代价极高(极少见),此时提前move可避免冗余拷贝——但这种情况,往往该反思的是类设计本身。

最后说个容易踩的坑:别试图用make_unique去“升级”已有裸指针。比如你手里有个int* p = new int(42);,别想着auto up = std::make_unique<int>(*p);——这创建的是副本,原指针还是悬空的。make_unique只负责“从零开始”,不接手历史债务。有遗留裸指针?先用unique_ptr接管,再逐步重构。

写到这儿,想起以前同事吐槽:“C++怎么连创建个智能指针都要学新函数?”
后来他改用make_unique重构了三处资源管理模块,上线后OOM告警降了70%。他没再提“多此一举”,只默默把make_unique加进了组内代码规范第一条。

make_unique不炫技,不抢镜。它就像你工位抽屉里那把磨得发亮的螺丝刀——不声不响,但每次拧紧关键螺丝时,你都庆幸它就在那儿。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,1774人围观)

还没有评论,来说两句吧...

目录[+]