Menu
Coddy logo textTech

多重継承

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 53/104。

C++では、クラスが複数の基底クラスから同時に継承することが可能です。これは多重継承と呼ばれ、異なるクラス階層の機能を組み合わせることができます。

複数のクラスから継承するには、それらをカンマで区切ります。

class Printer {
public:
    void print() {
        std::cout << "Printing document" << std::endl;
    }
};

class Scanner {
public:
    void scan() {
        std::cout << "Scanning document" << std::endl;
    }
};

class MultiFunctionDevice : public Printer, public Scanner {
public:
    void copy() {
        scan();
        print();
    }
};

MultiFunctionDeviceクラスは、PrinterScannerの両方のメソッドにアクセスできるようになりました:

MultiFunctionDevice mfd;
mfd.print();  // Printerから
mfd.scan();   // Scannerから
mfd.copy();   // 独自のメソッド

コンストラクタは、継承宣言に基本クラスが記述されている順序(左から右)で呼び出され、デストラクタは逆の順序で呼び出されます。初期化リストで各基本クラスのコンストラクタに引数を渡すことができます:

class MultiFunctionDevice : public Printer, public Scanner {
public:
    MultiFunctionDevice() : Printer(), Scanner() {}
};

両方の基底クラスが同じ名前のメンバを持っている場合に、潜在的な問題が発生します。これにより曖昧さが生じるため、どちらを指しているのかを指定するためにスコープ解決演算子を使用する必要があります。

class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};

C obj;
obj.A::show();  // Aのバージョンを呼び出す
obj.B::show();  // Bのバージョンを呼び出す

多重継承は強力ですが、特に基底クラスが共通の祖先を共有している場合には、複雑な状況を招く可能性があります。その課題については、次のレッスンで詳しく見ていきましょう。

challenge icon

チャレンジ

簡単

多重継承を使用して、複数の基底クラスの機能を組み合わせたスマートホームデバイスシステムを構築しましょう。WiFiに接続でき、かつ音声で制御できるデバイスを作成し、これらの機能を統合したスマートスピーカーを作成します。

コードは3つのファイルに分けて構成します:

  • WiFiDevice.h: ネットワークに接続可能なあらゆるデバイスを表す WiFiDevice クラスを定義します:
    • protected メンバ std::string networkName
    • ネットワーク名を受け取り、それを保存するコンストラクタ
    • Connecting to WiFi: <networkName> と出力する public メソッド connect()
    • WiFi Status: Connected と出力する public メソッド status()
  • VoiceControl.h: 音声機能を備えたあらゆるデバイスを表す VoiceControl クラスを定義します:
    • protected メンバ std::string assistantName
    • アシスタント名を受け取り、それを保存するコンストラクタ
    • <assistantName> is listening... と出力する public メソッド listen()
    • Voice Status: Active と出力する public メソッド status()
  • main.cpp: 3つの入力(それぞれ別々の行)を読み込みます:
    1. デバイス名 (string)
    2. WiFiネットワーク名 (string)
    3. 音声アシスタント名 (string)

    WiFiDeviceVoiceControl の両方を public 継承する SmartSpeaker クラスを定義します:

    • private メンバ std::string deviceName
    • 3つのパラメータすべてを受け取り、初期化リストを使用してネットワーク名を WiFiDevice に、アシスタント名を VoiceControl に渡し、デバイス名を保存するコンストラクタ
    • <deviceName> powering on... と出力し、その後に connect()listen() を呼び出す powerOn() メソッド
    • デバイス名を出力し、スコープ解決演算子を使用して両方の status() メソッドを呼び出し、曖昧さを解決する showAllStatus() メソッド

    入力値を使用して SmartSpeaker オブジェクトを作成します。powerOn() を呼び出し、空行を出力してから、showAllStatus() を呼び出します。

例えば、入力が EchoHomeNetworkAlexa の場合:

Echo powering on...
Connecting to WiFi: HomeNetwork
Alexa is listening...

Echo Status:
WiFi Status: Connected
Voice Status: Active

showAllStatus() メソッドは、デバイス名に続けて " Status:" を1行に出力し、その後、両方の基底クラスが同じ名前のメソッドを持っているため、曖昧さを避けるために WiFiDevice::status()VoiceControl::status() を使用して各基底クラスの status() メソッドを呼び出す必要があります。

チートシート

C++は多重継承をサポートしており、1つのクラスが複数の基底クラスから同時に継承することを可能にします:

class DerivedClass : public BaseClass1, public BaseClass2 {
    // クラス本体
};

派生クラスは、すべての基底クラスのメンバにアクセスできます:

class Printer {
public:
    void print() {
        std::cout << "Printing document" << std::endl;
    }
};

class Scanner {
public:
    void scan() {
        std::cout << "Scanning document" << std::endl;
    }
};

class MultiFunctionDevice : public Printer, public Scanner {
public:
    void copy() {
        scan();   // Scannerから
        print();  // Printerから
    }
};

基底クラスのコンストラクタは、継承リストに記述された順序(左から右)で呼び出されます。各基底クラスのコンストラクタに引数を渡すには、初期化リストを使用します:

class MultiFunctionDevice : public Printer, public Scanner {
public:
    MultiFunctionDevice() : Printer(), Scanner() {}
};

複数の基底クラスが同じ名前のメンバを持っている場合は、スコープ解決演算子を使用して曖昧さを解消します:

class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};

C obj;
obj.A::show();  // Aのバージョンを呼び出す
obj.B::show();  // Bのバージョンを呼び出す

自分で試してみよう

#include <iostream>
#include <string>
#include "WiFiDevice.h"
#include "VoiceControl.h"

using namespace std;

// TODO: WiFiDevice と VoiceControl の両方を公開継承する SmartSpeaker クラスを定義する
// - プライベートメンバ: deviceName (string)
// - コンストラクタ: deviceName, networkName, assistantName を受け取る
//   初期化リストを使用して、networkName を WiFiDevice に、assistantName を VoiceControl に渡す
// - powerOn() メソッド: "<deviceName> powering on..." と出力し、その後 connect() と listen() を呼び出す
// - showAllStatus() メソッド: "<deviceName> Status:" と出力し、その後両方の status() メソッドを呼び出す
//   スコープ解決演算子 (WiFiDevice::status() および VoiceControl::status()) を使用する

class SmartSpeaker {
    // TODO: ここで SmartSpeaker クラスを実装する
};

int main() {
    string deviceName, networkName, assistantName;
    getline(cin, deviceName);
    getline(cin, networkName);
    getline(cin, assistantName);

    // TODO: 入力値を使用して SmartSpeaker オブジェクトを作成する
    // TODO: powerOn() を呼び出す
    // TODO: 空行を出力する
    // TODO: showAllStatus() を呼び出す

    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン