第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 風スタイル。