C++におけるダイアモンド継承

  • C++におけるダイアモンド継承の実現方法と,注意点などをまとめる
  • 特に特別な書き方はなく,普通に継承するだけ
    • この場合,Zの親Yaと親Ybは,それぞれ異なる[基底Xのインスタンス]が親となる
#include <iostream>
 
class X {
 public:
  X(int id) : id_{id} { }
  int id() const noexcept { return id_; }
 private:
  int id_;
};
 
class Ya : public X {
 public:
  Ya() : X(0) { }
};
class Yb : public X {
 public:
  Yb() : X(1) { }
};
 
class Z : public Ya, public Yb {
 public:
  Z() { }
};
 
 
int main() {
    Z z;
    // std::cout << z.id() << std::endl;                 // error: 'id' is ambiguous
    std::cout << static_cast<Ya&>(z).id() << std::endl;  // = 0
    std::cout << static_cast<Yb&>(z).id() << std::endl;  // = 1
    return 0;
}
  • Z型からX型に直接アクセスすることができない
    • Yaの基底X,あるいは,Ybの基底Xのどちらにアクセスするべきかが曖昧なため
  • 型を明示的にYaまたはYbに変換することで,基底Xが1つに定まり,アクセスすることができる
z.id()                    // error: 'id' is ambiguous
static_cast<Ya&>(z).id()  // ok
static_cast<Yb&>(z).id()  // ok
  • 例えZがXを新たに継承したとしても,Xのシンボルにアクセスすることはできない
    • Zの基底X,Yaの基底X,Ybの基底Xを区別するための言語機能がないため,曖昧さを解決できないから
    • g++だとエラーにはならないが,警告対象
  • この問題は後述の仮想継承を使えば解決できる
// warning: direct base 'X' inaccessible in 'Z' due to ambiguity 
class Z : public X, public Ya, public Yb {
 public:
  Z() : X(2) { }
};
  • virtualキーワードを使って基底Xを仮想継承をすることで,Zのインスタンスに関わる基底Xのインスタンスを唯一とすることができる
    • つまり,Yaの基底X,Ybの基底Xを同一のインスタンスとすることができる
  • 基底Xの唯一のインスタンスのコンストラクタは,Xを仮想継承したクラスを直接的あるいは間接的に継承しているクラスの全てから呼ばれなければならない
    • ZのコンストラクタはXのコンストラクタを呼ばなければならず,唯一のXのインスタンスはZから呼ばれたコンストラクタによって初期化される
    • Zを通常継承したWが存在した場合でも,WはXのコンストラクタを呼ばなければならず,Wのインスタンス内部の唯一のXのインスタンスは,Wから呼ばれたコンストラクタによって初期化される
  • YaあるいはYbのどちらか一方のみで仮想継承をすると,YaとYbのみを継承するZから,Xにアクセスすることはできない
    • g++だと警告対象
    • 仮想継承のXのインスタンスと,Ybの基底Xのインスタンスとの間の曖昧さは,明示的キャストで解決できそうだがなぜか警告対象
#include <iostream>
 
class X {
 public:
  X(int id) : id_{id} { }
  int id() const noexcept { return id_; }
 private:
  int id_;
};
 
class Ya : virtual public X {
 public:
  Ya() : X(0) { }
};
class Yb : virtual public X {
 public:
  Yb() : X(1) { }
};
 
class Z : public Ya, public Yb {
 public:
  Z() : X(2) { }
};
 
class W : public Z {
 public:
  W() : X(3) { }
};
 
int main() {
    Z z;
    std::cout << z.id() << std::endl;                    // = 2
    std::cout << static_cast<Ya&>(z).id() << std::endl;  // = 2
    std::cout << static_cast<Yb&>(z).id() << std::endl;  // = 2
    W w;
    std::cout << w.id() << std::endl;                    // = 3
    std::cout << static_cast<Ya&>(w).id() << std::endl;  // = 3
    std::cout << static_cast<Yb&>(w).id() << std::endl;  // = 3
    std::cout << static_cast<Z &>(w).id() << std::endl;  // = 3
    return 0;
}
  • Last modified: 12 months ago