C++ Learning

第46回 多重継承と菱形継承

C++ は複数の基底クラスを同時に継承できる数少ない言語。便利な反面、菱形継承(ダイヤモンド問題)という悩ましい問題も抱えます。実務では多重継承は使わず、単一継承 + 純粋インターフェース(抽象クラス)の多重実装が推奨されます。

このページで押さえること
✅ 最低限ここだけ覚える
  • class Z : public A, public B {} で多重継承
  • 菱形継承でメンバが重複する
  • 解決には virtual 継承が必要
  • 実務では インターフェースの多重実装 として使うのが主流
⭐ 余裕があれば読む
  • mixin パターン
  • 多重継承時の vtable
  • 名前衝突と解決方法
  • Java などが多重継承を禁止した理由

1. 基本 ― 多重継承の書き方

多重継承C++
class Swim { public: void swim() { std::cout << "泳ぐ\n"; } }; class Fly { public: void fly() { std::cout << "飛ぶ\n"; } }; // 両方から継承 class Duck : public Swim, public Fly { }; Duck d; d.swim(); // OK d.fly(); // OK

複数の基底を「コンマ区切り」で並べるだけで、すべてのメンバを継承します。

2. 菱形継承(ダイヤモンド問題)

同じ基底から派生した 2 つのクラスを両方継承すると、基底のメンバが2 つに増える厄介な状況が生まれます。

菱形の形:
Animal
↗ ↖
Swimmer Flyer
↖ ↗
Duck
⚠ Duck は Animal を2 つ持ってしまう
問題コード曖昧
class Animal { public: std::string name_; }; class Swimmer : public Animal { }; class Flyer : public Animal { }; class Duck : public Swimmer, public Flyer { }; Duck d; // d.name_ = "ドナルド"; ← エラー: 2 つある name_ のどちらか曖昧 d.Swimmer::name_ = "ドナルド"; // 明示すれば可能 d.Flyer::name_ = "ダック";

3. virtual 継承による解決

基底をvirtual 継承すると、1 つのインスタンスに統合されます。

virtual 継承解決
class Animal { public: std::string name_; }; class Swimmer : virtual public Animal { }; // virtual 継承 class Flyer : virtual public Animal { }; // virtual 継承 class Duck : public Swimmer, public Flyer { }; Duck d; d.name_ = "ドナルド"; // OK: name_ は 1 つだけ存在
virtual 継承のコスト:
  • メモリが少し増える(仮想基底へのオフセットポインタ)
  • アクセスが少し遅くなる
  • コンストラクタの扱いが複雑(最派生クラスが基底を直接初期化)
使わない設計が優先。

4. 実務での推奨 ― インターフェース多重実装

C++ で多重継承が有用なのは、データメンバを持たない純粋仮想クラス(インターフェース)を複数実装する場合。これなら菱形問題は起きません。

インターフェース多重実装推奨
class IPrintable { public: virtual void print() const = 0; virtual ~IPrintable() = default; }; class ISerializable { public: virtual std::string serialize() const = 0; virtual ~ISerializable() = default; }; // 両方を実装 class User : public IPrintable, public ISerializable { public: void print() const override { /* ... */ } std::string serialize() const override { /* ... */ return ""; } };
モダン C++ のイディオム:
  • 単一継承(is-a 関係の基底 1 つ)
  • 複数のインターフェース実装(契約だけ提供するクラス)
これで Java の「extends 1 つ + implements 多数」と同じスタイルが実現できます。
ここまでで多重継承は OK
次は STEP 7 の確認問題、その後 STEP 8 テンプレート編へ。

確認クイズ

多重継承を 4 問で確認。

Q1. 多重継承の構文として正しいのは?

class C extends A, B { };
class C : public A, public B { };
class C : public A + public B { };
class C : A, B { };(public がなくてもよい)
: public A, public B が正しい構文。それぞれに public を書く必要があります(書かないと private 継承になる)。

Q2. 菱形継承(ダイヤモンド問題)とは?

継承階層が 4 段以上になる問題
共通の基底を持つ 2 つのクラスを両方継承すると、基底が重複する問題
コンパイル速度の問題
仮想関数が曖昧になる問題
A を継承する B と C があり、D が B と C の両方を継承すると、D の中に A が 2 つ存在する(メンバも 2 倍)。これを「菱形(ダイヤモンド)問題」と呼びます。

Q3. 菱形継承の解決方法は?

派生クラスで再度定義する
中間クラスで virtual 継承を使う
final を使う
解決できない(C++ の欠陥)
中間クラス(B, C)から基底 A をvirtual 継承すると、D では A が 1 つに統合されます。class B : virtual public A {};

Q4. C++ で多重継承を安全に使う推奨パターンは?

深い継承階層で複雑な関係を表現
多重継承は禁止されている
全ての基底に virtual 継承を付ける
単一継承 + 純粋インターフェース(抽象クラス)の多重実装
データメンバを持たない抽象インターフェース(純粋仮想関数のみ)なら菱形問題が起きず、安全に多重継承できます。「extends は 1 つ、implements は複数」の Java 風スタイル。