質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.04%

インターフェースクラスを仮想継承して実装する理由

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 5,529

JADEN

score 103

C++の教科書(ロベールのC++入門講座p655)で、インターフェースクラスを仮想継承して実装していました。
実装する際は、仮想継承である必要はないと思うのですが、なぜ仮想継承しているのでしょうか。
試しに、class CNumber : public IInt, public IDoubleとしましたが、動きました。

class IIntとclass IDoubleがclass IBaseを仮想継承している理由は分かります。
仮想継承でないと、class CNumberからのGetStringメンバ関数の呼び出しがあいまいになるからです。
(恐らく、インターフェースクラスに共通のインターフェースを与えるために、class IBaseを仮想継承しているのだと思います)

#include <iostream>
#include <string>

class IBase {
public:
    virtual ~IBase() {}
    virtual std::string GetString() const = 0;
};

class IInt : public virtual IBase {
public:
    virtual int GetInt() const = 0;
};

class IDouble : public virtual IBase {
public:
    virtual double GetDouble() const = 0;
};

class CNumber : public virtual IInt, public virtual IDouble {
public:
    CNumber(int intData, double doubleData) : m_int(intData), m_double(doubleData) {
    }
    std::string GetString() const {
        return std::string("CNumber");
    }
    int GetInt() const {
        return m_int;
    }
    double GetDouble() const {
        return m_double;
    }

private:
    int m_int;
    double m_double;
};

int main() {
    CNumber cnumber(2, 3.0);

    IBase& ibase = cnumber;
    std::cout << ibase.GetString() << std::endl;

    IInt& iint = cnumber;
    std::cout << iint.GetString() << std::endl;
    std::cout << iint.GetInt() << std::endl;

    IDouble& idouble = cnumber;
    std::cout << idouble.GetString() << std::endl;
    std::cout << idouble.GetDouble() << std::endl;
}


そもそも、仮想継承はダイヤモンド継承を作るために行うものだと思うので、class IIntとclass IDoubleをclass CNumberが仮想継承すると、クラス図が書けなくなるのではないでしょうか。
以下に、その理由を説明します。

class IIntとclass IDoubleがclass IBaseを仮想継承せず、class CNumberも両者を仮想継承しなければ、クラス図は、図1です。
多重継承
図1. 多重継承

class IIntとclass IDoubleがclass IBaseを仮想継承して、class CNumberは両者を仮想継承しなければ、クラス図は図2です。
ダイヤモンド継承
図2. ダイヤモンド継承

class IIntとclass IDoubleがclass IBaseを仮想継承して、class CNumberも両者を仮想継承すると、クラス図は、図3の様になり?、いまいち理解できません。
意味不明なクラス図
図3. 意味不明な継承

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

class IIntとclass IDoubleがclass IBaseを仮想継承して、class CNumberも両者を仮想継承すると、クラス図は、図3の様になり?、いまいち理解できません。

図3のような状況にはなりません。CNumberクラスがIIntIDoubleを仮想継承する/しないにかかわらず、クラス継承図は図2の通りになります。(Chironianさんの回答と同じ)

クラス継承関係におけるvirtualキーワードは、「あるクラス定義からみた全ての基底クラスのうち、virtualキーワード付きで派生した同じクラス型同士を、それぞれ1つの仮想基底にまとめる」働きとなります。

CNumberクラス定義からさかのぼった場合、(virtualを無視すれば)1つのIInt, 1つのIDouble, 2つのIBaseとなりますが(図1)、実際にはvirtual指定によりIBaseが1つにまとめられます(図2)。


実装する際は、仮想継承である必要はないと思うのですが、なぜ仮想継承しているのでしょうか。
試しに、class CNumber : public IInt, public IDoubleとしましたが、動きました。 

おっしゃる通り、このケースでは仮想継承する意義がさほどないと思います。実際、よくあるダイアモンド継承の説明では、ここ(public IInt, public IDouble)を仮想継承にはしませんし。

本当の理由は著者に聞かないとわかりませんが、「インターフェースクラス」という扱いをしていることから、あらゆるクラス継承関係上で実体が複数個現れることがないよう、予防的に常にvirtual派生としているのかもしれません。例えばJava言語のinterfaceと同じ扱いとしたいならば、「インターフェースクラス」=無条件にvirtual派生するという設計方針には一定の妥当性があります。

とはいえ、C++はJavaではありませんから、この設計が常にベストかと言われると大いに疑問はあります。少なくとも、仮想継承はメモリ使用量と処理速度に関して追加のオーバーヘッドが発生するため、むやみやたらに使わない方が良いのは確かです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/08 22:52

    回答ありがとうございます。

    キャンセル

+2

こんにちは。

IIntとIDoubleが1つになろうとするわけではないです。
もう1つ別にCNumberExがIIntとIDoubleをvirtual継承し、更にCSuperNumberがCNumberとCNumberExを継承したら、IIntとIDoubleがダイヤモンド継承されてしまいますが、virtualキーワードが付いているのでIIntとIDoubleとIBaseはそれぞれ1つだけ存在するようになると言うことです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/08 22:53

    回答ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる