为什么要使用std :: make_unique而不是std :: unique_ptr :: make?

26

C++为什么采用自由函数:

std::make_unique(...);
std::make_shared(...);

不要使用静态成员函数:

std::unique_ptr::make(...); // static
std::shared_ptr::make(...); // static

?


2
@M.M 谢谢,显而易见的队长。问题是:为什么没有 make 函数。 - vladon
1
@M.M 在我写的地方,为什么你使用了这个? - vladon
4
我只是喜欢那三个没有喝咖啡因的开发者答错了问题,我也是其中之一。大家早上好 ;) - Quentin
1
@vladon 因为 这些 类型从它们的参数中推导出了 pair/tuple 的内容,这就是它们的全部原理 - 您可以编写 std::pair<int, float>{42, 3.14f} :) - Quentin
6
试试看。编写一个模板类,提供一个成员函数,然后编写使用它的代码,并确保编译通过,你就会明白为什么了。 - Pete Becker
显示剩余3条评论
3个回答

25
TL;DR:静态成员函数始终可以访问私有数据,但只有在显式标记为friend时,自由函数才能访问私有数据。选择将这些函数实现为自由函数(其中少数实现为友元函数)不是随意的历史遗物,而是有意为之,以提高封装性并为所有std::make_x函数保持一致的命名方案。

在C++中有许多标准工厂函数

std::make_pair
std::make_tuple
std::make_unique
std::make_shared //efficiency
std::make_exception_ptr //efficiency
std::make_move_iterator
std::make_reverse_iterator
std::make_error_code
std::make_error_condition
//And several more are proposed for C++17

对于上述所有内容,只使用x的公共接口即可正确实现make_x函数。在make_sharedmake_exception_ptr的情况下,最有效的实现需要访问std::shared_ptrstd::exception_ptr的内部数据。其他所有功能都可以仅使用公共接口实现,零性能损失。
将这些函数实现为非友元自由函数减少了访问对象私有内部的代码量(一种理想的属性,因为当较少的代码访问私有数据时,需要审核违反对象不变式的操作的地方更少,并且如果对象的内部发生更改,则需要更改的地方也更少)。
如果make_shared是唯一的类似工厂函数,那么它成为成员函数可能是有意义的,但由于大多数此类函数不需要成为friend函数才能有效运行,为了保持一致性,make_shared也实现为自由函数。
这是正确的设计,如果始终使用静态成员函数make,除了make_sharedmake_exception_ptr之外的每种情况下,成员函数将不可避免地过度访问x对象的私有数据。采用标准化设计后,只有少数需要访问私有数据的make_x函数可以标记为friend,其余默认正确尊重封装性。如果在某些情况下使用非成员make_x,在其他情况下使用静态成员make,则标准库将变得不一致且更难以学习。

@Caleth:是的,我正在更新答案。扩展列表只会让观点更加清晰明了。 - Mankarse
4
《Effective C++》第23条:更喜欢使用非成员、非友元函数。这其实就是面向对象的基础封装知识。 - Matthieu M.

17

一致性.

我认为没有什么必要使用::make语法而不是当前的语法。我假设make_uniquemake_shared被优先选择,以保持与现有std::make_pairstd::make_heap函数的一致性,这些函数在C++11之前就已经存在了。


请注意,std::make_pair具有一个很大的优点:它自动从函数调用中推断出结果对的类型:

auto p0 = std::make_pair(1, 1.f); // pair<int, float>

如果我们有std::pair::make,那么我们将不得不编写:

auto p1 = std::pair<int, float>::make(1, 1.f);

这将违背make_pair的本意。


  • 因此,我认为选择make_uniquemake_shared是因为开发人员已经习惯了make_pair和类似的函数。

  • make_pair被选择而不是使用pair::make 是因为前者有上述的好处。


1
但问题不在于 make_pair,而是关于 make_unique。无论如何,在 unique_ptr<T>::makemake_unique<T> 中都必须指定返回类型。 - David Haim
8
@DavidHaim 相似事物的一致性是一种有价值的特性。 - Caleth
4
在过去,由于参数推断,选择了make_pair而不是可能存在的pair::make。后来可能选择了make_unique而不是unique_ptr::make,因为已经存在make_pair - Vittorio Romeo
2
此外,至少有一些委员会成员更喜欢非成员非友元函数而不是成员函数:http://www.gotw.ca/gotw/084.htm - Caleth
2
@DavidHaim 如果所有东西都按照你的期望行事,标准库就更容易学习了。因此,鉴于 std::make_pair 已经存在,库的用户会期望创建函数是以 std::make_... 开头的独立函数。 - Galik
显示剩余2条评论

3
除了传统惯例外,没有任何实际原因 - 静态类函数在功能上可以完成全局函数的所有功能。
C++ 偏爱包含在定义的命名空间中的全局函数(用于实用程序函数)。
其他编程语言(如 Java)喜欢静态公共函数,因为它们不支持全局函数。

这对于 make_*** 并不新鲜,还有其他例子存在:

std::this_thread::XXXX 取代 std::thread::XXXX_current
即使将与当前执行线程相关的函数作为静态函数放置在 thread 类中是有意义的,但它们是全局性质的,位于 this_thread 命名空间内。

同样,我们可以像 std::container::sort 这样拥有一个帮助容器的辅助类 std::container,但我们有的是 std::sort


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接