多重継承を説明するため、新しく規模の小さいプログラムを作ります。
今回はファイル分割しませんので、cppファイルが1つあれば十分です。
まず「基底クラス」を作りましょう。
<sample program cpp076-01>
#include <iostream> class Magician { public: void Fire() const; }; int main() { Magician magician; magician.Fire(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } |
<実行結果>
火の魔法! 続行するには何かキーを押してください・・・
「魔法使い」クラスを作りました。
メンバ関数は、使える魔法をイメージしています。
前回のドラゴンとミノタウロスと同じように、この「魔法使い」クラスから2つのクラスを派生させます。
<sample program cpp076-02>
#include <iostream> class Magician { public: void Fire() const; }; class WhiteMagician : public Magician { public: void Heal() const; }; class BlackMagician : public Magician { public: void Thunder() const; }; int main() { WhiteMagician whiteMagician; whiteMagician.Fire(); whiteMagician.Heal(); BlackMagician blackMagician; blackMagician.Fire(); blackMagician.Thunder(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } |
<実行結果>
火の魔法! 回復魔法! 火の魔法! 雷の魔法! 続行するには何かキーを押してください・・・
「白魔法使い」と「黒魔法使い」を作りました。
それぞれ独自の魔法(メンバ関数)を持っていますが、「魔法使い」も継承していますから「火の魔法」は共通して使えます。
「多重継承」とは、複数のクラスを継承して新しいクラスを作り出す事を指します。
今回は、「白魔法使い」と「黒魔法使い」の2つを組み合わせたクラスを作ってみます。
これが出来れば「火の魔法」「回復魔法」「雷の魔法」の3つが使えるようになるはずです。
<sample program cpp076-03>
#include <iostream> class Magician { public: void Fire() const; }; class WhiteMagician : public Magician { public: void Heal() const; }; class BlackMagician : public Magician { public: void Thunder() const; }; class RedMagician : public WhiteMagician, public BlackMagician { public: }; int main() { RedMagician redMagician; redMagician.Fire(); redMagician.Heal(); redMagician.Thunder(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } |
<コンパイル結果>
error C2385: 'Fire' へのアクセスがあいまいです。 note: 'Fire' (ベース 'Magician' 内) である可能性があります note: または、'Fire' (ベース 'Magician' 内) である可能性があります
コンパイルエラーになりました。
まず、新しいクラスの宣言から見てみます。
class RedMagician : public WhiteMagician, public BlackMagician { public: }; |
2つ以上のクラスを継承するには、「 , 」で区切ればOKです。
コンパイル結果を見ると、3つ呼び出した関数の中で、Fire関数のところがエラーになっています。
メッセージを見ると「あいまい」と言う言葉が書いてあります。
何が「あいまい」なのでしょうか?
これはダイヤモンド継承という問題で、イメージを書くと↓のような感じです。
+----------+ | 魔法使い | +----------+ / \ / \ +----------+ +----------+ |白魔法使い| |K魔法使い| +----------+ +----------+ \ / \ / +----------+ |赤魔法使い| +----------+ |
この図を見ると「赤魔法使い」への継承は2系統あるのが分かります。
魔法使い → 白魔法使い → 赤魔法使い
という系統と、
魔法使い → K魔法使い → 赤魔法使い
です。
この2系統は、内部ではまったく異なる管理が行われています。
「赤魔法使い」のFire関数を呼び出そうとした場合、どちらの系統の関数を呼び出せば良いか判断できないのです。
これが「あいまい」というメッセージの理由です。
これを解決するには「仮想継承」を使います。
「魔法使い」を継承している「白魔法使い」「K魔法使い」の継承方法を変更します。
「赤魔法使い」のクラスに書いてある継承の部分に「virtual」キーワードを付けます。
<sample program cpp076-04>
#include <iostream> class Magician { public: void Fire() const; }; class WhiteMagician : virtual public Magician { public: void Heal() const; }; class BlackMagician : virtual public Magician { public: void Thunder() const; }; class RedMagician : public WhiteMagician, public BlackMagician { public: }; int main() { RedMagician redMagician; redMagician.Fire(); redMagician.Heal(); redMagician.Thunder(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } |
これでエラーはなくなりましたので、実行してみましょう。
<実行結果>
火の魔法! 回復魔法! 雷の魔法! 続行するには何かキーを押してください・・・
ちゃんと3つの魔法が使えるようになりました。
もう少しケースを変えて試してみましょう。
「魔法使い」の「Fire関数」を「K魔法使い」のみオーバーライドしてみます。
<sample program cpp076-05>
#include <iostream> class Magician { public: virtual void Fire() const; }; class WhiteMagician : virtual public Magician { public: void Heal() const; }; class BlackMagician : virtual public Magician { public: void Fire() const; void Thunder() const; }; class RedMagician : public WhiteMagician, public BlackMagician { public: }; int main() { RedMagician redMagician; redMagician.Fire(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } void BlackMagician::Fire() const { std::cout << "炎の魔法!" << std::endl; } |
main関数は「Fire関数」の呼び出しだけにしました。
コンパイルすると「警告」が出ています。
<コンパイル結果>
warning C4250: 'RedMagician': 2 つ以上のメンバーが同じ名前を持っています。'BlackMagician::BlackMagician::Fire' から継承します。 note: 'BlackMagician::Fire' の宣言を確認してください
「魔法使い」の「Fire関数」と「K魔法使い」の「Fire関数」を継承しているため、区別が付かないようです。
しかし、勝手に「BlackMagicianクラス」の「Fire関数」を継承しているようです。
何となくですが「赤魔法使い」は「黒魔法使い」を継承していますから、「黒魔法使い」の「Fire関数」が「優先」されるのは分かるような気がします。
実行してみましょう。
<実行結果>
炎の魔法! 続行するには何かキーを押してください・・・
「K魔法使い」の「Fire関数」が呼ばれている事が分かります。
では、「魔法使い」の「Fire関数」は使えないのでしょうか?
main関数に次のコードを追加してみてください。
int main()
{
RedMagician redMagician;
redMagician.Fire();
redMagician.Magician::Fire();
return 0;
}
|
<実行結果>
炎の魔法! 火の魔法! 続行するには何かキーを押してください・・・
相変わらず「警告」は出ていますが、「魔法使い」の「Fire関数」を使う事が出来ました。
関数呼び出しの前に「所属」を書く事で呼び出しています。
※警告が出たまま使うのは気持ち悪いですけどね・・・
main関数の中を再度確認します。
redMagician.Fire(); redMagician.Magician::Fire(); |
2つ目の呼び出しは、はっきりと「魔法使い」の「Fire関数」を使うと書いてあります。
どちらの「Fire関数」を使うか決まっているのであれば「仮想継承」を止めるという選択肢があります。
「白魔法使い」と「K魔法使い」の継承に付けた「virtual」を消しましょう。
さらに、main関数の
redMagician.Fire(); |
も消します。(消さないとエラーになります)
<sample program cpp076-06>
#include <iostream> class Magician { public: virtual void Fire() const; }; class WhiteMagician : public Magician { public: void Heal() const; }; class BlackMagician : public Magician { public: void Fire() const; void Thunder() const; }; class RedMagician : public WhiteMagician, public BlackMagician { public: }; int main() { RedMagician redMagician; redMagician.Magician::Fire(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } void BlackMagician::Fire() const { std::cout << "炎の魔法!" << std::endl; } |
<実行結果>
火の魔法! 続行するには何かキーを押してください・・・
エラーが無くなりました。
仮想継承を止めるとダイヤモンド継承の問題が浮上すると思いますが、はっきりと継承元を書いておく事でコンパイラが判断出来るようにしています。
ついでに「白魔法使い」にもオーバーライドしてみましょう。
<sample program cpp076-07>
#include <iostream> class Magician { public: virtual void Fire() const; }; class WhiteMagician : public Magician { public: void Fire() const; void Heal() const; }; class BlackMagician : public Magician { public: void Fire() const; void Thunder() const; }; class RedMagician : public WhiteMagician, public BlackMagician { public: }; int main() { RedMagician redMagician; redMagician.Magician::Fire(); redMagician.WhiteMagician::Fire(); redMagician.BlackMagician::Fire(); return 0; } void Magician::Fire() const { std::cout << "火の魔法!" << std::endl; } void WhiteMagician::Heal() const { std::cout << "回復魔法!" << std::endl; } void WhiteMagician::Fire() const { std::cout << "温熱療法!" << std::endl; } void BlackMagician::Thunder() const { std::cout << "雷の魔法!" << std::endl; } void BlackMagician::Fire() const { std::cout << "炎の魔法!" << std::endl; } |
火の魔法! 温熱療法! 炎の魔法! 続行するには何かキーを押してください・・・
特に「警告」も「エラー」も出ませんでした。
これなら、前にやった「ドラゴン」と「ミノタウロス」の能力を受け継いだ「キマイラ」クラスなども作れそうです。
しかし、「元のクラス」を指定しなければならないと言う制約がありますので汎用性は失われます。