Effective C++ メモ
第 6 章 継承とオブジェクト指向設計
32 項 public 継承は is-a 関係を表すようにしよう
覚えておくこと
- public 継承は is-a 関係を意味する。派生クラスのオブジェクトは、基底クラスのオブジェクトの一種と考えられるので、基底クラスに適用できるものは、すべて、派生クラスに適用できるようにしなければならない。 これはそのままの意味ですが、厳密にこれを守りなさい。ということですね。
33 項 継承した名前を隠蔽しないようにしよう
覚えておくこと
-
基底クラス内にある名前は、派生クラス内に同じ名前があると、隠蔽される。public 継承では、それは望ましくない。 これは、基底クラスと同じ名前が派生クラスにあると、オーバーロードされた関数などを含めて隠蔽されてしまうということです。
-
隠蔽された名前を可視にするには、using 宣言か「仕事を送る関数」を使うとよい。 これは、基底クラスの名前を可視化するために using <名前> を使うか、派生クラスの関数から同名の基底クラス関数を呼び出すということです。名前>
34 項 インターフェースの継承と実装の継承の区別をしよう
覚えておくこと
- インターフェースの継承と実装の継承は違う。public 継承においては、派生クラスは基底クラスのインターフェースをすべて継承する。
- 純粋仮想関数は、インターフェースのみの継承を意味する。
- 単なる仮想関数は、(純粋でない仮想関数)は、インターフェースと「デフォルトの実装」の継承を意味する。
- 非仮想関数は、インターフェースと「変えてはならない実装」の継承を意味する。 それぞれの継承の意味を理解して使いましょう。
35 項 仮想関数の代わりになるものを考えよう
覚えておくこと
-
仮想関数の代替案には、NVI イディオムやストラテジパターンのいろいろな実装方法が適用できる。NVI イディオムは、テンプレートメソッドパターンの適用例になっている。 NVI イディオムは、非仮想関数から private な仮想関数を呼び出し、非仮想化する手法のこと。
-
ある機能の実現手段を、メンバ関数からクラス外の関数に移す方法もある。しかし、その方法には、「クラス外の関数は、クラスの public でないメンバにアクセスできない」という短所もある。 メンバ関数外にするので、当然 private な要素にはアクセスできない。
-
function オブジェクトは、関数ポインタを拡張したものと考えることができる。これは、指定したシグネチャだけでなく、それに変換可能なシグネチャを持つものも格納できる。 function オブジェクトは関数オブジェクト。
36 項 非仮想関数を派生クラスで再定義しないこと
覚えておくこと
- 非仮想のメンバ関数を派生クラスで再定義してはならない。 これは、ポインタや参照が基底クラスを指している場合と、派生クラスを指している場合で呼び出される関数が変わってしまい、is-a 関係が壊れてしまうからですね。基底クラスのデストラクタを仮想関数にしなければならないのもこのためです。
37 項 継承された関数のデフォルト引数値を再定義しない
覚えておくこと
- デフォルト引数値は静的結合で、仮想関数(再定義してもよい関数)は動的結合。それゆえ、派生クラスで関数のデフォルト引数値を変更しないこと。 これは、基底クラスの仮想関数に与えたデフォルト引数を派生クラスでも定義してしまうと、ポインタや参照を使用した際に場合によっては基底クラスのデフォルト引数が使用されてしまい混乱してしまうためです。
38 項 コンポジションを使って has-a 関係、is-implemented-in-terms-of 関係を作ろう
覚えておくこと
-
コンポジションは public 継承とはまったく異なる意味を持つ。 これは下記の通りです。
-
アプリケーション領域では、コンポジションは has-a 関係を意味し、実装領域では、コンポジションは is-implemented-in-terms-of 関係を意味する。 has-a は所有で、is-implemented-in-terms-of は構成要素として含んでいるということ。
39 項 private 継承は賢く使おう
覚えておくこと
-
private 継承は is-implemented-in-terms-of 関係(実装関係)を意味する。たいていの場合、コンポジションに劣るが、派生クラスが基底クラスの protected なメンバにアクセスする必要がある場合や仮想関数をオーバーライドする必要がある場合に意味がある。 これは、private 継承が基底クラスの関数をすべて private にし、実装の一部として使用する(is-a 関係ではない)ため。しかし、設計上はコンポジションのほうが優れている場合が多い。
-
コンポジションと違い、private 継承では、EBOが働くことがある。これは、オブジェクトのサイズをなるべく小さくしようとするライブラリ開発者には重要かもしれない。 EBOとは、サイズ 0 のクラスでも C++ ではサイズ 0 にはならないが、private 継承の基底クラスとして使用するクラスはサイズが 0 になる。その基底クラスが、関数や変数を持たなくても typedef や enum、static なデータメンバを持つことが多い。
40 項 多重継承は賢く使おう
覚えておくこと
- 多重継承は単一継承より複雑。また、曖昧さの原因ンとなり、仮想継承が必要ななるかもしれない。
-
仮想継承は、サイズ、効率、初期化と代入の複雑さにおいて、コストが大きい。実際的な判断としては、仮想基底クラスにはデータを持たせないのがよい。 仮想継承は下記のようなもの。
class DClass : virtual public BClass {};
- 多重継承には正当な使い方がある。たとえば、いんたーふぇーすクラスからの public 継承と実装を助けるクラスからの private 継承を考える、というシナリオがある。 インターフェースとの多重継承ならありということ。