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

已知應用[编辑]

相關的慣用語[编辑]