引数有りコンストラクタを持つクラスを仮想継承する際の注意

ロベールのC++入門講座 13-04

引数有りのコンストラクタを持つクラスを仮想継承し、それを多重継承した場合のお話です。

以下の例を見てみましょう。

#include <iostream>
using std::cout; using std::endl;

class CBase {
public:
    CBase (int num) {
        m_num = num;
    }
    int Get () const {
        return m_num;
    }
private:
    int m_num;
};

// 仮想継承
class CSuperA : virtual public CBase {
public:
    CSuperA () : CBase(1) {}
};

// 仮想継承
class CSuperB : virtual public CBase {
public:
    CSuperB () : CBase(2) {}
};

// 多重継承(ダイアモンド継承)
class CSuperC : public CSuperA, public CSuperB {};

int main () {
    CSuperA a_obj;
    CSuperB b_obj;
    CSuperC c_obj;

    cout << a_obj.Get() << endl;
    cout << b_obj.Get() << endl;
    cout << c_obj.Get() << endl;
    
    return 0;
}

この場合、CSuperAとCSuperBのインスタンス化はできますがCSuperCのインスタンス化はできずにコンパイルエラーがでます。

$ cl /W4 /EHs main.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
main.cpp(40) : error C2512: 'CBase::CBase' : クラス、構造体、共用体に既定のコン
ストラクタがありません。
        コンパイラでのこの診断により関数 'CSuperC::CSuperC(void)' が生成されました。

もしCBaseのコンストラクタに引数がなければ各クラスのコンストラクタが順番に呼ばれて終了だったのですが、CBaseのコンストラクタに引数があるので、CSuperAからの呼び出しかCSuperBからの呼び出しかが判断付かない状態になります。

仮想継承でCBaseの実体はひとつしか存在してはならないはずなのにCBase(1)の呼び出しとCBase(2)の呼び出しが存在するという矛盾が発生するから問題なんですね。

これを解決するにはCSuperCでCBaseのコンストラクタを呼び出してあげる必要があります。

// 多重継承(ダイアモンド継承)
class CSuperC : public CSuperA, public CSuperB {
public:
    // CBaseのコンストラクタを呼び出す
    CSuperC () : CBase(3) {}
};
$ main
1
2
3

こうした場合、なんとCSuperAとCSuperBのCBaseの呼び出しが無効となります。

変な感じはしますが、CSuperCのコンストラクタを呼び出した時点でCBase(3)が呼ばれ実体化されるので無視されるというイメージでしょうか。非常に複雑な仕様ですね。