string_view
operator+
vs. StrCat()
absl::Status
std::bind
absl::optional
and std::unique_ptr
absl::StrFormat()
make_unique
and private
Constructors.bool
explicit
= delete
)switch
Statements Responsibly= delete
AbslHashValue
and Youcontains()
std::optional
parametersif
and switch
statements with initializersinline
Variablesstd::unique_ptr
Must Be MovedAbslStringify()
vector.at()
auto
for Variable Declarationsmake_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.
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
.
We recommend either of these alternatives:
new
and absl::WrapUnique
, but explain your choice. For example,// Using `new` to access a non-public constructor. return absl::WrapUnique(new Widget(...));
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
.
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?
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: