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)可以產生更複雜的程式慣用語。