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.
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++