yohhoyの日記

技術的メモをしていきたい日記

コンストラクタ/デストラクタ×仮想関数呼び出し

C++においてコンストラクタ/デストラクタからの仮想関数呼び出しそれ自体はwell-definedだが、おそらくC++プログラマの期待する振る舞いではない。

バグの温床になりえるため、大抵のコーディング規約で禁止している(はず)。かつてClangに本件を検知する警告オプションが提案*1されたが放棄された模様。

struct Base {
  Base() {
    // コンストラクタ中での仮想関数呼び出しは
    // B::vfを呼び出す(D::vfではない!)
    vf();  
  }
  void g() {
    // 通常(非static)メンバ関数中からであれば
    // オーバーライドされたD::vfを呼び出す
    vf();
  }
  virtual void vf() { /*...*/ }
};

struct Derived : Base {
  virtual void vf() override { /*...*/ }
};

Derived obj;
obj.g();

純粋仮想関数(pure virtual function)が呼び出される場合は未定義動作(undefined behavior)を引き起こす。*2

struct Interface {
  Interface() {
    vf();  // NG: 純粋仮想関数の呼び出し!
  }
  virtual void vf() = 0;
};

struct Concrete : Interface {
  virtual void vf() override { /*...*/ }
};

Concrete obj;

プログラムのバグであることが自明なため、GCCやClangはコンパイル時に警告として報告する。MSVCの場合はプログラム実行時エラー。

  • GCC:pure virtual 'virtual void Interface::vf()' called from constructor
  • Clang:warning: call to pure virtual member function 'vf' has undefined behavior; overrides of 'vf' in subclasses are not available in the constructor of 'Interface'

C++17 15.7/p3より引用(下線部は強調)。C++11/14では12.7/p4。

Member functions, including virtual functions (13.3), can be called during construction or destruction (15.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class's non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor's or destructor's class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (8.2.5) and the object expression refers to the complete object of x or one of that object's base class subobjects but not x or one of its base class subobjects, the behavior is undefined. [Example:

struct V {
  virtual void f();
  virtual void g();
};

struct A : virtual V {
  virtual void f();
};

struct B : virtual V {
  virtual void g();
  B(V*, A*);
};

struct D : A, B {
  virtual void f();
  virtual void g();
  D() : B((A*)this, this) { }
};

B::B(V* v, A* a) {
  f();     // calls V::f, not A::f
  g();     // calls B::g, not D::g
  v->g();  // v is base of B, the call is well-defined, calls B::g
  a->f();  // undefined behavior, a's type not a base of B
}

-- end example]

関連URL

*1:https://reviews.llvm.org/D56366 New warning call-to-virtual-from-ctor-dtor when calling a virtual function from a constructor or a destructor

*2:C++17 13.4/p6: "Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined."