本周小贴士#134:make_unique与私有构造函数

Posted -飞鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#134:make_unique与私有构造函数相关的知识,希望对你有一定的参考价值。

作为totw#134最初发表于2017年5月10日
由谷歌工程师Yitzhak Mandelbaum创作

因此,你阅读了小贴士#126并准备留下一些新的东西。一切都是正常的,直到你尝试使用absl::make_unique并使用私有构造函数去构造对象,但是编译失败。让我们看一下这个问题的一个具体示例,以便理解哪里出了问题。然后,我们可以讨论一些解决方案。

示例:制造小部件

你正在定义一个类来展示小部件。每个小部件都有一个标识符,这些标识符受某些约束。为了确保一直满足这些约束,你将Widget类的构造函数声明为私有,并为用户提供工厂函数Make,用于生成具有合适标识符的小部件。(有关为什么工厂函数优于初始化方法的建议,请参阅技巧 #42。)

class Widget 
 public:
  static std::unique_ptr<Widget> Make() 
    return absl::make_unique<Widget>(GenerateId());
  

 private:
  Widget(int id) : id_(id) 
  static int GenerateId();

  int id_;

当你尝试编译时,你将得到如下的错误:

error: calling a private constructor of class 'Widget'
     return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); 
                                 ^
note: in instantiation of function template specialization
'absl::make_unique<Widget, int>' requested here
    return absl::make_unique<Widget>(GenerateId());
                ^
note: declared private here
  Widget(int id) : id_(id) 

当make已经访问私有构造函数时,absl::make_unique无法工作!注意,这个问题也出现在友元下。例如:Widget的友元使用absl::make_unique去构造Widget会有同样的问题。

建议

我们推荐以下的任一个替代方案:

  • 使用new和absl::WrapUnique,但请解释你的选择。例如:Widget的友元使用absl
        // 使用new访问非仅有构造函数
        return absl::WrapUnique(new Widget(...));
    
  • 考虑构造函数是否可以安全地公开。如果是这样,那么在适合直接构造时将其公开并记录。

在很多情况下,将构造函数标记为私有是过度设计。在这些情况下,最好的解决方案是将你的构造函数标记为公有并记录它们的正确使用。然而,如果你的构造函数需要设置为私有(比如说,为了确保类不变量),则使用new和WrapUnique。

为什么我不能友元absl::make_unique?

你可能想友元absl::make_unique,这样它就可以访问你的私有构造函数。这是一个糟糕的主意,有几个原因:

首先,虽然对友元实践的全面讨论超过出本贴士的范围,但是一个好的经验法则是“不要远距离的朋友关系”。否则,你将创建一个友元竞争声明,一个不由所有者维护的声明。另外请参见风格指南建议。

其次,请注意,你依赖于absl::make_unique的实现细节,即它直接调用new。如果它被重构以间接调用new——例如,在构造C++14或更高版本中,absl::make_unique是std::make_unique的别名,并且友元声明是无效的。

最后,通过友元absl::make_unique,你允许任何人以这种方式创建对象,那么为什么不将你的构造函数声明为公有,然后完全避免这个问题呢?

std::shared_ptr呢?

对于std::shared_ptr,情况有些不同。此处没有absl::WrapSahred和模拟(std::shared_ptr(new T(…)))涉及两个分配,其中std::make_shared可以用一个来完成。如果这种差异很重要,那么请考虑习惯用法:让构造函数采用只有特定代码才能创建的特殊标记。例如:

class Widget 
  class Token 
   private:
    Token() 
    friend Widget;
  ;

 public:
  static std::shared_ptr<Widget> Make() 
    return std::make_shared<Widget>(Token, GenerateId());
  

  Widget(Token, int id) : id_(id) 

 private:
  static int GenerateId();

  int id_;
;

关于习惯用法,参考以下文章:
More Useful Empty Classes
Passkey Idiom and Better Friendship in C++

以上是关于本周小贴士#134:make_unique与私有构造函数的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#144:关联容器中的异构查找

本周小贴士#144:关联容器中的异构查找

本周小贴士#74:委托和继承构造函数

本周小贴士#135:测试约定而不是实现

本周小贴士#116: 拷贝消除与值传递

本周小贴士#116: 拷贝消除与值传递