インタフェースクラス (C++をもう一度)
[最終更新] (2019/06/03 00:41:49)

概要

C++にはJavaなどと異なりインタフェースという機能が存在しません。C++では純粋仮想関数仮想デストラクタしかメンバをもたないクラスをインタフェースとして利用します。その際、多重継承や仮想継承の知識が必要になります。

多重継承

#include <iostream>
using namespace std;

class MyClass {
public:
    virtual ~MyClass() {} // 仮想デストラクタ (インラインメンバ関数として)
    virtual void Show() = 0;
    virtual void SShow() = 0;
    void Set(int intval) {
        m_intval = intval;
    }

protected:
    int m_intval;
};

class MySubClassA :
    public MyClass
{
public:
    virtual ~MySubClassA() {}
    virtual void Show() {
        cout << "MySubClassA::Show(" << m_intval << ")" << endl;
    }
    virtual void SShow() {
        cout << "MySubClassA::SShow" << endl;
    }
};

class MySubClassB :
    public MyClass
{
public:
    virtual ~MySubClassB() {}
    virtual void Show() {
        cout << "MySubClassB::Show(" << m_intval << ")" << endl;
    }
    virtual void SShow() = 0;
};

class MySubSubClass : // 多重継承。MySubClassA, MySubClassB を介して
    public MySubClassA, // MyClass を二つ継承している。MyClass の実体が二つ。
    public MySubClassB
{
public:
    virtual void SShow() {
        cout << "MySubSubClass::Show("
             << MySubClassA::m_intval << ","
             << MySubClassB::m_intval << ")" << endl;
    }
};

int main() {
    MySubSubClass ss;
    MySubClassA& sA = ss; // アップキャスト
    MySubClassB& sB = ss;
    MyClass& bA = static_cast<MySubClassA&>(ss); // アップキャスト (二段飛び)
    MyClass& bB = static_cast<MySubClassB&>(ss); // (曖昧さ回避のため経由する一段目にまずキャスト)

    // 実体は二つあるため別々の値を設定可能
    sA.Set(1); // 「MySubClassA経由のMyClass」
    sB.Set(-1); // 「MySubClassB経由のMyClass」

    sA.Show(); //=> MySubClassA::Show(1)
    sB.Show(); //=> MySubClassB::Show(-1)
    bA.Show(); //=> MySubClassA::Show(1)
    bB.Show(); //=> MySubClassB::Show(-1)

    // SubSubで実装すると二つの実体の両方がオーバライドされる
    ss.SShow(); //=> MySubSubClass::Show(1,-1)
    sA.SShow(); //=> MySubSubClass::Show(1,-1)
    sB.SShow(); //=> MySubSubClass::Show(1,-1)
    bA.SShow(); //=> MySubSubClass::Show(1,-1)
    bB.SShow(); //=> MySubSubClass::Show(1,-1)

    return 0;
}

仮想継承

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass(int intval) : m_intval(intval) {}
    virtual ~MyClass() {}
    virtual void Show() = 0;

protected:
    int m_intval;
};

class MySubClassA :
    virtual public MyClass // 仮想継承
{
public:
    MySubClassA() : MyClass(1) {} // 継承における引数のあるコンストラクタ
    virtual ~MySubClassA() {}     // https://www.qoosky.io/techs/3fef7fa668
    virtual void Show() {
        cout << m_intval << flush;
    }
};

class MySubClassB :
    virtual public MyClass // 仮想継承
{
public:
    MySubClassB() : MyClass(-1) {}
    virtual ~MySubClassB() {}
};

class MySubSubClass :
    public MySubClassA, // 多重継承
    public MySubClassB  // (MyClassの実体は共有され一つ。ダイヤモンド継承)
{
public:
    MySubSubClass() : MyClass(0) {} // MySubClassA, MySubClassB のどちらで MyClass()
    // を実行したらよいか曖昧なため MySubSubClass で実行。仮想継承のはまりどころです。

    virtual void Show() { // 仮に MySubClassA, MySubClassB の両方で Show を実装したと
        // すると一つの MyClass に対して Show の実体が二つになりエラーです。
        // MySubSubClass で Show を実装するとエラー回避できます。
        cout << "MySubSubClass::Show(" << flush;
        MySubClassA::Show();
        cout << "," << MySubClassB::m_intval << ")" << endl;
    }
};

int main() {
    MySubSubClass ss;

    MySubClassA& sA = ss;
    MySubClassB& sB = ss;
    MyClass& b = ss; // 実体は一つであるため直接二段飛びのアップキャストが可能

    // sA経由でもsB経由でも同じ実体
    ss.Show(); //=> MySubSubClass::Show(0,0)
    sA.Show(); //=> MySubSubClass::Show(0,0)
    sB.Show(); //=> MySubSubClass::Show(0,0)
    b.Show(); //=> MySubSubClass::Show(0,0)

    // こうすると、MyClassのコンストラクタの引数が異なることに注目
    MySubClassA _sA;
    // MySubClassB _sB; // MySubClassBは抽象クラスなためエラー (Show が未実装)
    _sA.Show(); cout << endl; //=> 1 (← 0でないことがポイントですね)

    return 0;
}

インタフェースクラス

この続きが気になる方は

インタフェースクラス (C++をもう一度)

残り文字数は全体の約 25 %
tybot
100 円
関連ページ