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



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

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

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

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

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,则标准库将变得不一致且更难学习。

一致性

我认为没有任何令人信服的理由使用::make语法而不是当前语法。我假设make_uniquemake_shared比静态::make函数更可取,以与 C++11 之前存在的现有std::make_pairstd::make_heap函数保持一致。


请注意,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是为了上述好处。

除了约定之外,没有一个具体的原因—— 静态类函数可以做全局函数可以做的所有事情(功能明智)。
C++首选包含在已定义命名空间中的全局函数(对于实用程序函数)。
其他编程语言(如Java)更喜欢静态公共函数,因为不支持全局函数。

这对make_***来说并不新鲜,其他例子也存在:

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

此外,我们可以有类似std::container::sort的东西,std::container是容器的帮助程序类,但我们std::sort

最新更新