Reputation: 13852
Trying to allow make_unique
on a class with private ctor I came into the following strange difference between two cases:
class A {
int _i;
A(): _i(7) {}
public:
template<typename... T>
static std::unique_ptr<A> create(T&&... t) {
struct enablePrivateCtor : public A {
using A::A;
};
return std::make_unique<enablePrivateCtor>(std::forward<T>(t)...);
}
void doIt() const {
std::cout << _i << std::endl;
}
};
int main() {
auto a = A::create();
a->doIt();
}
Output:
7
class A {
int _i;
A(int i): _i(i) {} // <- change 1, ctor getting int
public:
// no change here!
template<typename... T>
static std::unique_ptr<A> create(T&&... t) {
struct enablePrivateCtor : public A {
using A::A;
};
return std::make_unique<enablePrivateCtor>(std::forward<T>(t)...);
}
void doIt() const {
std::cout << _i << std::endl;
}
};
int main() {
auto a = A::create(7); // <- change 2, sending 7
a->doIt();
}
Compilation Error:
unique_ptr.h: error: calling a private constructor of class 'enablePrivateCtor'
Why the 1st one - with the empty ctor - is OK, while the 2nd - the non-empty ctor - is not?
Upvotes: 4
Views: 1748
Reputation: 474366
The default constructor is never inherited. Therefore, the first enablePrivateCtor
generates a default constructor, which calls the base class default constructor.
When you inherit a constructor (as in the second case), the new constructor has the same access level as the inherited one. So since A::A(int)
is private, so too will be enablePrivateCtor::enablePrivateCtor(int)
. So you won't be able to construct with it.
If you need to have a private constructor be able to be called indirectly (through make_unique
/emplace
/etc), then you need to use a private key type. Like this:
class A;
class A_key
{
A_key() = default;
A_key(int) {} //Prevent `A_key` from being an aggregate.
friend class A;
};
class A {
int _i;
public:
A(int i, A_key): _i(i) {}
// no change here!
template<typename... T>
static std::unique_ptr<A> create(T&&... t)
{
return std::make_unique<A>(std::forward<T>(t)..., A_key{});
}
void doIt() const {
std::cout << _i << std::endl;
}
};
...
auto ptr = A::create(7);
A a(7, A_key{}); //Does not compile, since you're not a friend.
A_key
is publicly copyable, but it is not publicly default constructible. So non-private code can pass them around, but non-private code cannot create them.
Upvotes: 5
Reputation: 275896
The code you posted has undefined behavior.
In particular, you allocate an enablePrivateCtor
then delete an A
.
A better way than this is to use a key type.
class A {
int _i;
A(): _i(7) {}
class construction_token_t {
explicit construction_token_t(int) {}
friend class A;
};
static auto ctor_token() {
return construction_token_t(0);
}
public:
template<class...Args>
A( construction_token_t, Args&&...args ):A(std::forward<Args>(args)...){}
template<typename... T>
static std::unique_ptr<A> create(T&&... t) {
return std::make_unique<A>(ctor_token(), std::forward<T>(t)...);
}
void doIt() const {
std::cout << _i << std::endl;
}
};
We create a token which can grant another class the right to access our private ctor. The only one who can create this token is our class.
We then pass it to make_unique
.
An alternative is to use the factory lambda pattern.
template<class F>
struct factory_lambda_t {
F f;
template<class T>
operator T() const { return f(); }
};
template<class F>
factory_lambda_t<std::decay_t<F>>
factory( F&& f ) { return {std::forward<F>(f)}; }
In C++14 this requires move/copy ctors to be public, but in C++17 it doesn't.
class A {
int _i;
A(): _i(7) {}
public:
template<typename... T>
static std::unique_ptr<A> create(T&&... t) {
return std::make_unique<A>(factory([&]{
return A(std::forward<T>(t)...);
}));
}
void doIt() const {
std::cout << _i << std::endl;
}
};
which I think is pretty slick.
This may result in eliding constructors completely in some cases. In others, a single move occurs.
Upvotes: 2
Reputation: 67822
The difference is that enablePrivateCtor
automatically gets a default constructor (which is allowed to call A::A
).
It doesn't automatically get an integer conversion constructor: add
enablePrivateCtor(int i) : A(i) {}
and see it work.
Upvotes: 3