このページはC++における設計、実装におけるクラス間の関連をどのように表すかを定性的に判断できるようになる為に作りました。
まだ書きかけです。
用語の説明
包含(containment, 含有)
- オブジェクトが別のオブジェクトを内部に持つような構造のこと
- 別の言い方として、合成(composition)、層化(layering)、集約(aggregation)、埋め込み(embedding)がある
is-a関係
- Derived is a Baseが成り立つ関係
- public継承はこのis-a関係を表す
- Baseに適用できるものは、全てDerivedに適用しても問題ない(Ex. メンバ関数、関数、データ構造など)
- ポインタや参照を用いて、多様的に利用される
- Rectanbgle <- Squareの例がis-a関連ではないことで有名(Squareで幅のみを変更したときの挙動が矛盾してしまう)
has-a関係
- 「"ふがふが"が"ほげほげ"を持つ」と言える関係(Ex. 人は名前を持つ: Personオブジェクトは名前(string name)を持つ)
- 包含を用いて表される関連
- is-a関係との違いは簡単で、is-a関係が成り立っているかどうかでわかる(Ex. 人は名前である とは言えない(is-a関係ではない))
is-implemented-in-terms-of関係
- 注目しているクラスを別のクラスを用いて実装する場合に言える関係
- 包含、private継承を用いて表される関連
- 有名な例としてSetテンプレートをListテンプレートを用いて実装する Set is implemented in terms of list. がある
- is-a関係との違いは簡単で、is-a関係が成り立っているかどうかでわかる (Ex. SetとListに同じ値を2回挿入したときの挙動は全く異なるのでis-a関係ではない)
is-almost-a関係
- Exceptional C++内で出てきた(Ex. RectangleとSquare)
- この関係を表すためにpublic継承を用いては"絶対に"いけない
- そもそもis-almost-a関係が成り立つような処理は書かないほうがいいっぽい?(保守プログラマが派生クラスを多様的に用いてalmostのせいで氏ぬ)
クラス間の関連の選択肢
下に行く程関連度合いが強くなり、関連するクラスへの依存度が増す。
- 関連のあるクラスへのポインタ、参照を持つ(これだけちょっと別)
- 関連のあるクラスの実態をメンバに持つ
- 関連のあるクラスのfriendクラスになる
- 継承(private, protected, public)を使う
包含の特徴
- has-a関係、もしくはis-implemented-in-terms-of関係が成り立つときに用いる
- is-implemented-in-terms-of関係が成り立つ時とは、今実装しているクラスで役立つ機能があるクラスにあるが、そのクラスとの概念的なつながりがない場合。private継承である必要がなく(下記の private継承の特徴 を参照)、実装上のテクニックである
- has-a関係はアプリケーション領域の関係(personとname)であり、is-implemented-in-termes-of関係は実装領域の関係(setとlist)という違いがある
継承では実現しにくい包含のメリット
- 複数のインスタンスを保持することができる
- Pimplイディオムを用いてコンパイラファイアウォールの恩恵を受けることができる
- テンプレートで用いた場合に、テンプレートのユーザに実装をフレキシブルに変更してもらうことができるテンプレートを制作可能(Ex. 以下はExceptional C++の項目24のコードの一部)
template <class T, class Impl = MyList<T> >
class MySet3 {
public:
...
private:
Impl impl_;
};
public継承の特徴
- is-a関係が成り立つ場合に"のみ"使用してよい(例外として、拡張を実現する場合にもつかってよい?)
- is-implemented-in-terms-of関連をモデルにするために非public継承で十分な場合にpublic継承を用いては"絶対に"いけない
- is-almost-a関係をモデルにするためにも使ってはダメ(参照: 上記のis-almost-a関係)
- 非public継承に比べて基底クラスがダダ漏れ
private継承の特徴
- is-implemented-in-terms-of関係を意味する
- Derivedで役立つ機能がBaseにあるが、BaseとDerivedに概念的なつながりがなく、包含ではなくprivate継承でなければ解決できない問題がある場合のみ使って良い
- 要するにほとんどの場合包含で済む
- private継承が必要な場合にも、Baseのpublicメンバもしくはprotectedメンバにアクセスする必要があるDerivedのメンバがごく一部の場合は、そのメンバのみをカプセル化してカプセル化した方のみBaseを継承するようにする。そこで仮想関数のオーバーライドなり、protectedメンバにアクセスするなりすれば良い。こうする理由はDerivedのメンバにpublicとprotectedなBaseメンバがダダ漏れになるのは不必要に強い関連を与えてしまうため
private継承しなければならない場合
- 仮想関数のオーバーライドが必要
- クラスのprotectedメンバにアクセスしなければならない
- 空の基底クラスの最適化(empty base optimization, EBO)によるメモリの節約がどうしても必要(ライブラリ開発時とか。ほとんどの場合必要ない)
protected継承の特徴
- あんまり目立ってないが、protected継承するとBaseのpublicメンバとprotectedメンバがDerivedのprotectedメンバになる
多重継承について
参考文献
- Effective C++
- Exceptional C++
最終更新:2013年11月28日 16:04