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)可以产生更复杂的程式惯用语。

已知应用[编辑]

相关的惯用语[编辑]