为什么C++需要std::make_unique而非支持完美转发的unique_ptr构造函数?
问题描述
我并非要询问new operator相关的差异,请仔细阅读问题。
我想了解为什么我们需要专门的make_unique函数,而不直接为unique_ptr实现对应的特殊构造函数。
我们完全可以给unique_ptr添加如下构造函数,让make_unique不再有存在的必要:
template<typename T, typename ...TArgs> unique_ptr::unique_ptr(TArgs&&... args) : inner_ptr(new T(std::forward(args)...)) {}
解答
重载冲突问题
你提出的这个构造函数会和unique_ptr现有的接收裸指针的构造函数产生严重的重载歧义。比如当你传入的参数恰好是T*类型时,编译器无法判断你是要将这个裸指针的所有权交给unique_ptr,还是要把这个指针作为构造参数新建一个T对象,直接会导致编译失败,这是这个方案最根本的缺陷。数组特化不兼容
unique_ptr支持数组类型的特化,比如unique_ptr<int[]>可以用来管理动态数组。如果使用你设计的构造函数,无法区分构造数组时传入的长度参数和普通对象的构造参数,比如unique_ptr<int[]>(10),到底是要构造一个长度为10的int数组,还是要构造单个int对象赋值为10,完全无法区分。而make_unique通过单独的数组重载版本解决了这个问题,make_unique<int[]>(10)的语义非常明确,就是创建长度为10的数组。隐式转换风险
这种构造函数会允许隐式类型转换,比如某个函数参数是unique_ptr<int>类型,你直接传入一个int值就会被隐式构造出一个临时的unique_ptr<int>对象,完全违背了智能指针所有权需要显式声明的设计原则,很容易导致意外的内存分配、对象生命周期管理混乱的问题。语义区分需求
unique_ptr现有构造函数的语义非常明确:传入裸指针就是接管该指针的所有权。而直接构造新对象的语义和接管所有权的语义完全不同,揉在构造函数里会导致代码可读性大幅下降,其他人阅读代码时无法第一时间判断该行代码是接管已有指针还是新建对象。用单独的make_unique工厂函数可以明确区分两种语义,也和shared_ptr的make_shared设计保持了标准库接口的一致性。
内容的提问来源于stack exchange,提问作者Angelicos Phosphoros

