TL;DR - 两阶段建设很糟糕。尝试让你的构造函数构造东西,而不是调用任何虚拟方法,或者需要它才能运行。
如果您希望在对象构造(包括 vtable)之后进行初始化,则需要对对象进行单独的初始化阶段。
一个可能更好的处理方法是:
class Base
{
int x = 0; // notice the =0 here
public:
Base() {} // nothing
virtual setZero() {*this = Base{};} // use operator= to assign zeros
};
class Derived : public Base
{
double d = 0.; // notice the = 0. here
public:
Derived () { } // nothing
void setZero() override {*this = Derived{};}
};
我们也可以避免重写setZero:
template<class D, class B=void>
struct SetZero:B {
void setZero() override {
*static_cast<D*>(this) = D{};
}
};
template<class D>
struct SetZero<D,void> {
virtual void setZero() {
*static_cast<D*>(this) = D{};
}
};
现在我们可以:
class Base:public SetZero<Base>
{
int x = 0; // notice the =0 here
public:
A() {} // nothing
};
class Derived : public SetZero<Derived, Base>
{
double d = 0.; // notice the = 0. here
public:
Derived () { } // nothing
};
setZero 是为我们写的。
这里的 DRY 是 默认构造零,我们将零放在声明变量的旁边。 setZero 然后就变成了一个辅助方法,用默认构造的对象复制自己。
现在,在具有 vtable 的类上公开值语义复制/移动操作是一个糟糕的计划。因此,您可能希望保护复制/移动并添加好友声明。
template<class D, class B=void>
struct SetZero:B {
void setZero() override {
*static_cast<D*>(this) = D{};
}
SetZero()=default;
protected:
SetZero(SetZero&&)=default;
SetZero& operator=(SetZero&&)=default;
SetZero(SetZero const&)=default;
SetZero& operator=(SetZero const&)=default;
~SetZero() override=default;
};
template<class D>
struct SetZero<D,void> {
virtual void setZero() {
*static_cast<D*>(this) = D{};
}
SetZero()=default;
protected:
SetZero(SetZero&&)=default;
SetZero& operator=(SetZero&&)=default;
SetZero(SetZero const&)=default;
SetZero& operator=(SetZero const&)=default;
virtual ~SetZero()=default;
};
所以那些会变长。
在Base 和Derived 有vtables,建议添加
protected:
Derived(Derived&&)=default;
Derived& operator=(Derived&&)=default;
};
阻止外部访问移动/复制构造和移动/复制分配。无论您如何编写setZero,都建议您这样做(任何此类移动/复制都会有切片的风险,因此将其暴露给您班级的所有用户是一个糟糕的计划。这里我将其设为protected,因为setZero 依赖在它上面使归零干燥。)
另一种方法是两阶段构建。在其中,我们将所有“原始”构造函数标记为受保护。
class Base {
int x;
protected:
Base() {} // nothing
public:
virtual setZero() { x = 0; }
};
然后我们添加一个非构造函数的构造函数:
class Base {
int x;
protected:
Base() {} // nothing
public:
template<class...Ts>
static Base Construct(Ts&&...ts){
Base b{std::forward<Ts>(ts)...};
b.setZero();
}
virtual setZero() { x = 0; }
};
外部用户必须Base::Construct 才能获得Base 对象。这很糟糕,因为我们的类型不再是规则的,但是我们已经有了 vtable,这使得它一开始就不太可能是规则的。
我们可以 CRTP 它;
template<class D, class B=void>
struct TwoPhaseConstruct:B {
template<class...Ts>
D Construct(Ts&&...ts) {
D d{std::forward<Ts>(ts...));
d.setZero();
return d;
}
};
template<class D>
struct TwoPhaseConstruct<D,void> {
template<class...Ts>
D Construct(Ts&&...ts) {
D d{std::forward<Ts>(ts...));
d.setZero();
return d;
}
};
class Base:public TwoPhaseConstruct<Base> {
int x;
protected:
Base() {} // nothing
public:
virtual setZero() { x = 0; }
};
class Derived:public TwoPhaseConstruct<Derived, Base> {
int y;
protected:
Derived() {} // nothing
public:
virtual setZero() { Base::setZero(); y = 0; }
};
这里是兔子洞,如果你想make_shared 或类似的,我们必须添加一个辅助类型。
template<class F>
struct constructor_t {
F f;
template<std::constructible_from<std::invoke_result_t<F const&>> T>
operator T()const&{ f(); }
template<std::constructible_from<std::invoke_result_t<F&&>> T>
operator T()&&{ std::move(f)(); }
};
这让我们
auto pBase = std::make_shared<Base>( constructor_t{[]{ return Base::Construct(); }} );
但是你想去兔子洞多远?