为什么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_shared
和make_exception_ptr
的情况下,最有效的实现需要访问std::shared_ptr
或std::exception_ptr
的内部数据。所有其他方法都可以仅使用公共接口实现,性能损失为零。
将这些函数实现为非友元自由函数可以减少可以访问对象的私有内部的代码量(这是一个理想的属性,因为当较少的代码可以访问私有数据时,必须审核违反对象不变量的操作的位置就越少,如果对象的内部发生变化,则可能需要更改的位置也更少)。
如果make_shared
是唯一类似的工厂函数,那么将其作为成员函数可能是有意义的,但由于大多数此类函数不需要是friend
函数才能有效运行,因此为了保持一致性,make_shared
也作为自由函数实现。
这是正确的设计,就好像静态成员make
函数被一致地使用一样,那么在除了make_shared
和make_exception_ptr
之外的每种情况下,成员函数将不可避免地对x
对象的私有数据进行过度访问。通过标准化设计,需要访问私有数据的少量make_x
功能可以标记为friend
,其余功能默认正确封装。如果在某些情况下使用非成员make_x
,而在其他情况下使用静态成员make
,则标准库将变得不一致且更难学习。
一致性。
我认为没有任何令人信服的理由使用::make
语法而不是当前语法。我假设make_unique
和make_shared
比静态::make
函数更可取,以与 C++11 之前存在的现有std::make_pair
和std::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_unique
和make_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