More C++ Idioms/初始化期间调用虚函数
初始化期间呼叫虚函数 (Calling Virtuals During Initialization)
编辑
目的
编辑在物件初始化时,模拟调用虚函数。
别名
编辑初始化期间动态绑定的惯用语(Dynamic Binding During Initialization idiom)
动机
编辑有时候,当初始化子物件,子物件(derived object)想要调用子类别(derived classes)的虚函数。在C++语言明言禁止这样情况发生。因为,在物件未完成初始化前,呼叫子物件的成员函数是非常危险。当物件建构时,若虚函数未存取物件的资料成员,这样将不会产生问题。但是,它保证并不是通用的方法。
class Base {
public:
Base();
...
virtual void foo(int n) const; // 經常為純虛函數
virtual double bar() const; // 經常為純虛函數
};
Base::Base()
{
... foo(42) ... bar() ...
// 這裡將不會使用動態綁定
// 目的: 藉由呼叫這些虛函數模擬動態綁定
}
class Derived : public Base {
public:
...
virtual void foo(int n) const;
virtual double bar() const;
};
解决方案与范例程式
编辑达成预期的效果有多种方法。 每一个方法都有它的优缺点。 一般来说,这个方法主要分成两大类: 二阶段初使化(two phase initialization)和一阶段初使化(single phase initialization)。
二阶段初使化是将物件的建构从初始化的状态分开。这种分离并不总是能做得到。 在分离函数将物件状态的初始化凑在一起,此函数可以为成员函数或独立的函数。
class Base {
public:
void init(); // 有可能是或不是虛函數
...
virtual void foo(int n) const; // 經常為純虛函數
virtual double bar() const; // 經常為純虛函數
};
void Base::init()
{
... foo(42) ... bar() ...
// 大部分這些從原始的 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::auto_ptr <Base> factory (Parameter p)
{
std::auto_ptr <Base> ptr (new Derived (p));
p->init ();
return p;
}
在这里可以找到非样板函数的版本。工厂函数可以被移动到父类别里面,但此函数必须为静态(static)。
class Base {
public:
template <class D, class Parameter>
static std::auto_ptr <Base> Create (Parameter p)
{
std::auto_ptr <Base> ptr (new D (p));
p->init ();
return p;
}
};
int main ()
{
std::auto_ptr <Base> b = Base::Create <Derived> ("para");
}
为了防止使用者例外使用衍生类别的建构,它应该放置在私有函数。界面应该是很容易正确使用和很难错误使用 - 记得吗? 工厂函数必须为子类别的友谊。在成员创建函数,父类别应该子类别的友谊。
- 不使用二阶段初使化
使用辅助阶层(helper hierarchy)可达成预期的效果。 但是、这是并不希望维护额外类别的阶层。 传递指针到静态函数是C的风格(C'ish)。 奇怪地递回样板模式(Curiously Recurring Template Pattern idiom)可以被应用在此情况。
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()\n";
}
static void foo () {
cout << "Derived::foo()\n";
}
static void bar () {
cout << "Derived::bar()\n";
}
};
使用父类别是资料成员(Base-from-Member idiom)可以产生更复杂的程式惯用语。