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

Why std::make_unique instead of std::unique_ptr::make?

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

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

而不是使用静态成员函数:

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

?

一致性.

我认为没有任何令人信服的理由使用 ::make 语法而不是当前语法。我假设 make_uniquemake_shared 比静态 ::make 函数更受欢迎,以与现有的 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 是为了上述好处。

除了惯例之外没有具体原因 - static-class 函数可以做全局函数可以做的一切(功能方面)。
C++ 更喜欢包含在定义的命名空间内的全局函数(对于实用程序函数)。
其他编程语言(例如 Java)更喜欢静态 public 函数,因为不支持全局函数。

这对 make_*** 来说并不陌生,存在其他示例:

std::this_thread::XXXX 而不是 std::thread::XXXX_current
尽管将与当前执行线程相关的函数作为静态函数放在 thread [=30 中可能有意义=],它们在 this_thread 命名空间内成为全局的。

此外,我们可以有类似 std::container::sort 的东西,其中 std::container 是容器的助手 class,但我们有 std::sort

TL;DR:静态成员函数始终可以访问私有数据,但自由函数只有在显式标记为 friend 时才能访问私有数据。选择将这些函数实现为自由函数(将少量函数实现为友元函数)并不是随机的历史产物,而是一种深思熟虑的决定,目的是改进封装,同时为所有 std::make_x 函数。


C++中有many standard factory functions

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的public接口即可正确实现make_x功能。在 make_sharedmake_exception_ptr 的情况下,最有效的实现需要访问 std::shared_ptrstd::exception_ptr 的内部数据。所有其他的都可以仅使用 public 接口实现,性能损失为零。

将这些函数作为非友元自由函数来实现可以减少访问对象私有内部的代码量(a desirable property,因为访问私有数据的代码越少,访问私有数据的代码就越少必须审计违反对象不变量的操作的地方,以及更少的地方,如果对象的内部发生变化,则可能需要更改)。

如果 make_shared 是唯一类似的工厂函数,将它作为成员函数可能是有意义的,但由于大多数此类函数不需要是 friend 函数即可运行有效地,为了保持一致性,make_shared 也作为自由函数实现。

这是正确的设计,如果一贯使用静态成员make函数,那么除了make_sharedmake_exception_ptr之外的所有情况下,成员函数都难免有过多的访问 x 对象的私有数据。通过标准化设计,少数make_x个需要访问私有数据的函数可以标记为friend,其余的默认正确封装。如果在某些情况下使用非成员 make_x 而在其他情况下使用静态成员 make,标准库将变得不一致并且更难学习。