18.5 多重継承
第 15章「RWCollectable クラスの設計」で、RWCollectable を継承する Bus クラスを構築しました。すでに Bus クラスが存在すると仮定すると、以下のように多重継承を使って、Bus と RWCollectable の機能を持つ新しいクラスを作成し、手間を省くことができたはずです。
class CollectableBus : public RWCollectable, public Bus {
.
.
.
};
これは、Rogue Wave の多くのコレクションクラスで取られている手段です。例えば、RWCollectableString は RWCollectable と RWCString の両方のクラスを継承します。一般的な概念は、まずオブジェクトを作成し、それを RWCollectable クラスに付け加えて、全体をコレクション可能にすることです。この方法を使うと、RWCollectable クラスを継承したくないような他の場合にもオブジェクトを使用できます。
この手段をとる別の理由は、曖昧な基底クラスを避けることにあります。次に例を示します。
class A { };
class B : public A { };
class C : public A { };
class D : public B, public C { };
void fun(A&);
main () {
D d;
fun(d); // どちらの A ?
}
fun() の呼び出しから曖昧さを取り除くには、2 つのやり方があります。以下のように変更するか、
fun((B)d); // B の、A の発生
または、A を仮想基底クラスにすることです。
最初の方法は、適切なキャストを実行するためにユーザが継承階層の詳細を把握している必要があるため、エラーが発生しやすくなります。
A を仮想関数にするという 2 番目のやり方でこの問題は解決しますが、別の問題が発生します。派生元のクラスへキャストし戻すのがほとんど不可能になります。これは、継承階層に複数のパスができてしまうからです。物理的に説明すると、コンパイラが仮想基底クラスを基底クラスへのポインタとして実装し、ポインタを逆方向にはたどれないからです。
オブジェクトの継承階層で、一致を探して可能なパスをすべて検索しつくすことができます (これは、NIH クラスのやり方です)。ただし、キャストごとに検索しなければならないため、結果のアドレスを記憶することによって速度を上げたとしても、この検索は遅くなります。また、サイズが大きく常に複雑なため、この方法は受け入れられないと判断しました。
これらの理由で最初の方法に戻りました。同じ基底クラスからすべてのクラスを派生しないようにして、継承ツリーを単純にして受け入れることができるようにしました。多くの機能を持つ大きな基底クラスではなく、機能を別々の小さな基底クラスに分けることにしました。
目的は、まずオブジェクトを構築して、それから収集性などの必要な機能を基底クラスに付け加えることにあります。つまり、同じ型の複数基底クラスを避け、その結果、曖昧な呼び出しを避けることになります。