C++の仮想基底クラスの話です。これ、存在は知っていても使うことのない機能の筆頭ではないかと思います。

さて、このプログラムではBのコンストラクタにどんな値が渡されるでしょうか。

#include <iostream>
 
class B
{
public:
  explicit B(int x)
  {
    std::cout << "B::B " << x << std::endl;
  }
};
 
class Hoge : public virtual B
{
public:
  Hoge() : B(1) {}
};
 
class Piyo : public virtual B
{
public:
  Piyo() : B(1) {}
};
 
class D : public Hoge, public Piyo
{
public:
  D() : B(10), Hoge() {}
};
 
int main()
{
  D x;
}

答えは10です。そもそも、DはBから直接派生していないのに、DのコンストラクタでBに対する引数を指定できることを不思議に思うかもしれません。そう、virutal基底クラスは最派生クラスでコンストラクタへの引数を指定します。もし、DでBへの初期化子を書かなかったら、コンパイルエラーになります。HogeとPiyo、それぞれでBへの初期化子を与えており、どちらを採用すべきか曖昧、それを決定する責任を持つのは最派生クラスというわけです。

もちろん、この挙動は多重継承がない場合にも適用されます。次のプログラムでは、Bに2が渡されます。

#include <iostream>
 
class B
{
public:
  explicit B(int x)
  {
    std::cout << "B::B " << x << std::endl;
  }
};
 
class Hoge : public virtual B
{
public:
  Hoge() : B(1) {}
};
 
class D : public Hoge
{
public:
  D() : B(2), Hoge() {}
};
 
int main()
{
  D x;
}

私は、この最派生クラスで指定した初期化子が採用されるという規則、virtual関数のオーバーライドに通じるよなあ、とぼんやり思いました。なお、『C++の設計と進化』では、仮想基底クラスを表すキーワードがvirtualである理由について、virutal基底クラスのメンバ変数アクセスに、概念上virtual関数同様vtblを経由する必要があるからだ、という旨が書かれています(cf. §12.4.1 仮想ベースクラスのレイアウト)。

スポンサード リンク

この記事のカテゴリ

  • ⇒ 仮想基底クラス