更多 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;
}
};
使用基類-從-成員習語,可以建立更復雜的變化形式。