更多 C++ 习语/在初始化期间调用虚函数
外观
模拟在对象初始化期间调用虚函数。
初始化期间的动态绑定习语
有时需要在派生对象初始化时调用派生类的虚函数。语言规则明确禁止这样做,因为在派生对象的部分被初始化之前调用派生对象的成员函数是危险的。如果虚函数不访问正在构造的对象的数据成员,这不是问题,但没有通用的方法来确保这一点。
class Base {
public:
Base();
...
virtual void foo(int n) const; // Often pure virtual.
virtual double bar() const; // Often pure virtual.
};
Base::Base()
{
... foo(42) ... bar() ...
// These will not use dynamic binding.
// Goal: Simulate dynamic binding in those calls.
}
class Derived : public Base {
public:
...
virtual void foo(int n) const;
virtual double bar() const;
};
有多种方法可以实现预期效果。每种方法都有其优缺点。一般来说,这些方法可以分为两类。一类使用两阶段初始化,另一类只使用单阶段初始化。
两阶段初始化技术将对象构造与初始化其状态分开。这种分离并不总是可能的。对象的初始化状态被合并到一个单独的函数中,该函数可以是方法或独立函数。
class Base {
public:
void init(); // May or may not be virtual.
...
virtual void foo(int n) const; // Often pure virtual.
virtual double bar() const; // Often pure virtual.
};
void Base::init()
{
... foo(42) ... bar() ...
// Most of this is copied from the original Base::Base().
}
class Derived : public Base {
public:
Derived (const char *);
virtual void foo(int n) const;
virtual double bar() const;
};
template <class Derived, class Parameter>
std::unique_ptr <Base> factory (Parameter p)
{
std::unique_ptr <Base> ptr (new Derived (p));
ptr->init ();
return ptr;
}
工厂函数可以移动到基类中,但必须是静态的。
class Base {
public:
template <class D, class Parameter>
static std::unique_ptr <Base> Create (Parameter p)
{
std::unique_ptr <Base> ptr (new D (p));
ptr->init ();
return ptr;
}
};
int main ()
{
std::unique_ptr <Base> b = Base::Create <Derived> ("para");
}
派生类 Derived 的构造函数应该被设为私有的,以防止用户意外使用它们。接口应该易于正确使用,难以错误使用。然后,工厂函数应该是派生类的友元。如果是成员创建函数,则基类可以是派生类的友元。
使用辅助层次结构可以实现预期效果,但这需要维护额外的类层次结构,这是不希望的。传递指向静态成员函数的指针是 C 语言式的。在这种情况,奇异递归模板模式习语可能会有用。
class Base {
};
template <class D>
class InitTimeCaller : public Base {
protected:
InitTimeCaller () {
D::foo ();
D::bar ();
}
};
class Derived : public InitTimeCaller <Derived>
{
public:
Derived () : InitTimeCaller <Derived> () {
cout << "Derived::Derived()" << std::endl;
}
static void foo () {
cout << "Derived::foo()" << std::endl;
}
static void bar () {
cout << "Derived::bar()" << std::endl;
}
};
使用基类-从-成员习语,可以创建更复杂的变化形式。