Menu
Coddy logo textTech

仮想継承

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

ダイヤモンド問題は、class が共通の base class を共有する2つの class から inherits するときに発生します。特別な処理を行わないと、派生 class は共通の base のコピーを2つ持つことになり、曖昧さとメモリの無駄が生じます。

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // エラー: 曖昧 - どちらの 'age'?

Bat クラスには、2つの独立した Animal サブオブジェクトが含まれています。1つは Mammal を通じたもので、もう1つは Bird を通じたものです。これにより、この問題の名前の由来となったダイヤモンド型の inheritance 図が作られます。

仮想 inheritanceは、共通の base のコピーが1つだけ存在するようにすることで、この問題を解決します。共有 base から継承するときは、virtualキーワードを追加します。

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // 動作する!'age' は1つだけ存在する

Bat はコンストラクター内で Animal を直接初期化する必要があることに注意してください。仮想継承では、中間クラスに関係なく、最も派生したクラスが仮想基底クラスの構築を担当します。

challenge icon

チャレンジ

簡単

仮想継承がダイヤモンド問題をどのように解決するかを示す、worker 管理システムを構築しましょう。TeamLeadDeveloperManagerの両方から継承し、それらが共通のEmployee base classを共有する hierarchy を作成します。

コードを4つのファイルに分けて整理します。

  • Employee.h:共通の base classEmployeeを Defineします。
    • protected なstd::string nameint id
    • 両方の値を受け取り、次を出力する constructor:Employee [<name>] hired with ID <id>
    • 次を出力する public なgetInfo() method:Employee: <name> (ID: <id>)
    • 次を出力する virtual destructor:Employee [<name>] record closed
  • Developer.hEmployeeからvirtual public inheritanceを使用するDeveloper classを Defineします。
    • protected なstd::string language member
    • name、id、languageを受け取る constructor:nameとidをEmployeeに渡し、languageを保存して、次を出力します:Developer [<name>] specializes in <language>
    • 次を出力する public なcode() method:<name> is coding in <language>
    • 次を出力する destructor:Developer [<name>] signed off
  • Manager.hEmployeeからvirtual public inheritanceを使用するManager classを Defineします。
    • protected なint teamSize member
    • name、id、team sizeを受け取る constructor:nameとidをEmployeeに渡し、team sizeを保存して、次を出力します:Manager [<name>] leads a team of <teamSize>
    • 次を出力する public なmanage() method:<name> is managing <teamSize> people
    • 次を出力する destructor:Manager [<name>] stepped down
  • main.cpp:4つの inputs(それぞれ別の行)を読み取ります。
    1. Name(string)
    2. Employee ID(integer)
    3. Programming language(string)
    4. Team size(integer)

    DeveloperManagerの両方からpublicに継承するTeamLead classを Defineします。

    • 4つすべてのパラメーターを受け取り、まずEmployee(virtual base)を直接 initializeし、その後DeveloperManagerを initializeする constructor
    • constructor は次を出力する必要があります:TeamLead [<name>] ready to lead and code!
    • getInfo()code()manage()をこの順番で callするshowRole() method
    • 次を出力する destructor:TeamLead [<name>] promoted out

    block scope 内でTeamLead objectをCreateし、showRole()をcallしてから、scope外に出します。blockの後で、次を出力します:Organization restructured!

たとえば、inputsがAlice101C++5の場合:

Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!

Employeeのconstructor callとEmployeeのdestructor callがそれぞれ1回だけであることに注目してください。virtual inheritanceにより、共有されるbaseのcopyは1つだけ存在します。virtual inheritanceでは、最も派生したclassがvirtual baseの構築を担当するため、TeamLeadEmployeeを直接 initializeする必要があります。

自分で試してみよう

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Developer と Manager の両方から公開継承する TeamLead クラスを定義する
// 覚えておくこと: 仮想継承では、TeamLead は Employee(仮想基底)を直接初期化する必要がある
class TeamLead : public Developer, public Manager {
public:
    // TODO: name、id、language、teamSize を受け取るコンストラクタを実装する
    // 初期化必須: まず Employee(仮想基底)、次に Developer、そして Manager
    // Should print: TeamLead [<name>] ready to lead and code!
    TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
        : Employee(name, id),
          Developer(name, id, language),
          Manager(name, id, teamSize) {
        // TODO: コンストラクタのメッセージを出力する
    }

    // TODO: showRole() メソッドを実装する
    // getInfo()、code()、manage() をその順序で呼び出すこと
    void showRole() {
        // TODO: 3つのメソッドを呼び出す
    }

    // TODO: デストラクタを実装する
    // Should print: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: デストラクタのメッセージを出力する
    }
};

int main() {
    // 入力を読み取る
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: ブロックスコープ内に TeamLead オブジェクトを作成する
    // showRole()を呼び出し、その後スコープ外に出す
    {
        // TODO: TeamLeadを作成してshowRole()を呼び出す
    }

    // ブロックの後に最終メッセージを出力する
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz icon腕試し

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

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

自分で練習してみよう: C++オンラインコンパイラ