尝试在私有ctor的类上允许make_unique
我在两种情况下遇到以下奇怪的区别:
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();
}
输出:
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();
}
编译错误:
unique_ptr.h: error: calling a private constructor of class 'enablePrivateCtor'
为什么第一个 - 空的ctor - 可以,而第二个 - 非空的ctor - 不是?
答案 0 :(得分:4)
永远不会继承默认构造函数。因此,第一个enablePrivateCtor
生成一个默认构造函数,它调用基类的默认构造函数。
当您继承构造函数时(如第二种情况),新构造函数具有与继承的相同的访问级别。因为A::A(int)
是私有的,enablePrivateCtor::enablePrivateCtor(int)
也是如此。所以你将无法用它构建。
如果您需要能够间接调用私有构造函数(通过make_unique
/ emplace
/ etc),那么您需要使用私钥类型。像这样:
class A;
class A_key
{
A_key() = default;
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
是可公开复制的,但它不是公开默认的可构造的。因此非私有代码可以传递它们,但是非私有代码无法创建它们。
答案 1 :(得分:3)
区别在于enablePrivateCtor
会自动获取默认构造函数( 允许调用A::A
)。
它不会自动获得整数转换构造函数:add
enablePrivateCtor(int i) : A(i) {}
看到它有效。
答案 2 :(得分:2)
您发布的代码具有未定义的行为。
特别是,您分配enablePrivateCtor
,然后删除A
。
比这更好的方法是使用密钥类型。
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;
}
};
我们创建一个令牌,可以授予另一个类访问我们私人ctor的权利。唯一可以创建此令牌的是我们的类。
然后我们将其传递给make_unique
。
另一种方法是使用工厂lambda模式。
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)}; }
在C ++ 14中,这要求移动/复制ctors是公开的,但在C ++ 17中它并不是公开的。
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;
}
};
我认为非常光滑。
在某些情况下,这可能导致完全忽略构造函数。在其他情况下,会发生一次移动。