添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
刚失恋的投影仪  ·  Tip: Stretching list ...·  2 天前    · 
瘦瘦的烤红薯  ·  Quick Tip: SQLAlchemy ...·  2 周前    · 
豪情万千的芹菜  ·  Mastering React ...·  3 周前    · 
拉风的包子  ·  Tip and Tricks to ...·  3 周前    · 
有腹肌的苦瓜  ·  ERROR_CODE - ...·  1 月前    · 
狂野的投影仪  ·  java ...·  9 月前    · 

Tip of the Week #134: make_unique and private Constructors.

Originally posted as TotW #134 on May 10, 2017

By Yitzhak Mandelbaum, Google Engineer

Updated 2020-04-06

Quicklink: abseil.io/tips/134

So, you read Tip #126 and are ready to leave new behind. Everything’s going fine until you try to use std::make_unique to construct an object with a private constructor, and it fails to compile. Let’s take a look at a concrete example of this problem to understand what went wrong. Then, we can discuss some solutions.

Example: Manufacturing Widgets

You’re defining a class to represent widgets. Each widget has an identifier and those identifiers are subject to certain constraints. To ensure those constraints are always met, you declare the constructor of the Widget class private and provide users with a factory function, Make , for generating widgets with proper identifiers. (See Tip #42 for advice on why factory functions are preferable to initializer methods.)

class Widget { public: static std::unique_ptr<Widget> Make() { return std::make_unique<Widget>(GenerateId()); private: Widget(int id) : id_(id) {} static int GenerateId(); int id_;

When you try to compile, you get an error like this:

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 'std::make_unique<Widget, int>' requested here return std::make_unique<Widget>(GenerateId()); note: declared private here Widget(int id) : id_(id) {}

While Make has access to the private constructor, std::make_unique does not! Note that this issue can also arise with friends. For example, a friend of Widget would have the same problem using std::make_unique to construct a Widget .

Recommendations

We recommend either of these alternatives:

  • Use new and absl::WrapUnique , but explain your choice. For example,
  • // Using `new` to access a non-public constructor. return absl::WrapUnique(new Widget(...));
  • Consider whether the constructor may be safely exposed publicly. If so, make it public and document when direct construction is appropriate.
  • In many cases, marking a constructor private is over-engineering. In those cases, the best solution is to mark your constructors public and document their proper use. However, if your constructor needs to be private (say, to ensure class invariants), then use new and WrapUnique .

    Why Can’t I Just Friend std::make_unique (or absl::make_unique )?

    You might be tempted to friend std::make_unique (or absl::make_unique ), which would give it access to your private constructors. This is a bad idea , for a few reasons.

    First, while a full discussion of friending practices is beyond the scope of this tip, a good rule of thumb is “no long-distance friendships”. Otherwise, you’re creating a competing declaration of the friend, one not maintained by the owner. See also the style guide’s advice .

    Second, notice that you are depending on an implementation detail of make_unique , namely that it directly calls new . If it is refactored so that it indirectly calls new – for example, in build modes using C++14 and later absl::make_unique is an alias for std::make_unique , and the friend declaration is useless.

    Finally, by friending make_unique , you’ve allowed anyone to create your objects that way, so why not just declare your constructors public and avoid the problem altogether?

    What About std::shared_ptr ?

    The situation is somewhat different in the case of std::shared_ptr . There is no absl::WrapShared and the analog – std::shared_ptr<T>(new T(...)) – involves two allocations, where std::make_shared can be done with one. If this difference is important, then consider the passkey idiom : have the constructor take a special token that only certain code can create. For example,

    class Widget { class Token { private: explicit Token() = default; 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_;

    Note that we are providing an explicit defaulted constructor. This is needed for C++17 and earlier, as otherwise Widget::Token would be an aggregate and could be aggregate-initialized by {} , effectively bypassing the private access.

    For a full discussion of the passkey idiom, consult either of these articles:

  • Passkey Idiom: More Useful Empty Classes
  • Passkey Idiom and Better Friendship in C++
  •