Effective C++第六章--継承とオブジェクト向け設計ノート
10267 ワード
条項32:あなたのpublic継承モールドにおけるis-a関係を確定する
条項33:継承された名称を隠すことを避ける
派生クラスはベースクラスから継承され、名前付き役割ドメインネストが生成されます.派生クラスの内部には役割ドメインが作成されます.派生クラスが名前を使用する場合は、まず派生クラスの名前を付けて役割ドメインを検索します.派生クラスの内部に名前がない場合、ベースクラスの役割ドメインクラスで名前が検索されます.ベースクラスがない場合は、namespace、さらにグローバル役割ドメインに行きます.
派生クラスがベースクラスから継承されると、関数が定義され、関数名がベースクラスの関数名と同じになると、ベースクラスの同名の関数はすべて上書きされます.パラメータが一致するかどうかにかかわらず.これは命名規則だからです.
派生クラスがベースクラスを継承する場合、ベースクラスには同名で複数回再ロードされる関数があり、派生クラスはその一部を再定義または上書きし、usingを使用します.USingを使用すると、ベースクラスのすべての同名関数が上書きされないように、ベースクラスの一部と派生クラスのリロード関数を同時に存在させることができます.
次のようになります.
class base {
public:
void fun(int i) { std::cout<<"base"<<std::endl; }
};
class derived : public base{
public:
using base::fun;
void fun() { std::cout<<"derived"<<std::endl; }
};
このような使い方では、すべてのオーバーライドは発生しません.派生クラスは、異なるパラメータに基づいて呼び出しベースクラスまたは独自の関数を選択できます.
派生クラスはベースクラスのすべての関数を継承したくない場合があります.この場合、公有継承はis-aであるため、公有継承は使用できません.ベースクラス内のすべての同名関数が派生クラスに表示されるため、usingを使用することはできません.private継承プラスパス関数を使用して実装する必要があります.
class derived : private base { // ,
public:
virtual void fun() { base::fun(); }
};
上のコードは概略であり、このような形式で書くと、ベースクラスの関数を呼び出すことができます.
条項34:インタフェースの継承と継承の実現を区別する
条項35:virtual関数以外の選択肢を考慮する
Non-Virtual Interface手法によるTemplate Methodモードの実現
virtual関数はprivate関数としてnon-virtual関数を使用してprivate virtual関数を呼び出します.
例:
class game_character {
public:
int health_value() const {
... //
int ret = do_health_value(); //
... //
return ret;
}
private:
virtual int do_health_value() const { // ,
...
}
};
この設計は、non-virtual interface(NVI)手法とも呼ばれる.いわゆるTemplate Method設計モードの一種である.上のnon-virtual関数はwrapperと呼ぶことができる.
NVI手法の1つの利点は、仕事ができる前、後の仕事です.例えば,事前ロック,事後ロック解除,assert検証などである.
Funciton PointersによるStrategy(ポリシー)モードの実装
実際には,異なるニーズに対して異なる関数ポインタを用いてコールバックを行い,言うまでもない.
tr 1::functionでStrategyモードを完了
これは実際にstd::function、あるいはboost::functionです.この本は古いので、当時はまだありませんでした.
std::function相対関数ポインタの利点は全能にある!関数だけでなく、関数オブジェクト、メンバー関数も使用できます.ただしstd::bindとの連携に注意して、メンバー関数をバインドするには、このオブジェクトポインタを変更する必要があります.
クラシックなStrategyモード
純粋なコードでポリシー・モードを実現するには、次の手順に従います.
class game_character; //
class health_calc_func {
public:
virtual int calc(const game_character& gc) const
{ ... }
};
health_calc_func default_health_calc;
class game_character {
public:
explicit game_character(health_calc_func* phcf = &default_health_cal) : health_calc_(phcf)
{}
int health_value() const {
return health_calc_->calc(*this);
}
private:
health_calc_func* health_calc_;
};
以上がポリシーモード(ポリシーモードは本当に親しみやすい)であり、上記の方法でhealth_calc_func継承システムに派生クラスを組み込むだけで、新しいアルゴリズムを追加することができる.
条項36:継承されたnon-virtual関数を再定義しない
これは言うまでもなく、is-aに合致しなければならないからです.
条項37:継承されたデフォルトパラメータ値を再定義しない
コード検証:
class base {
public:
virtual void fun(int i = 3) { std::cout<<i<<std::endl; }
};
class derived : public base {
public:
virtual void fun(int i = 2) { std::cout<<"derived"<<std::endl; std::cout<<i<<std::endl; }
};
int main()
{
base *b = new derived;
b->fun();
return 0;
}
上記のコード印刷の結果は次のとおりです.
derived 3
これは完全にエラーの結果、派生クラス関数が呼び出されたのに、ベースクラスのデフォルト値が印刷されます.
条項38:複合成形型におけるhas-aまたは「あるものに基づいて実現する」
複合にはhas−aと「is−implement−in−terms−of」の2種類がある.has-aはある物にあるメンバーで、例えば教室、メンバー変数は机などで、これは理解しやすいです.主に後者は、あるものによって実現されることを意味する.例えば、自分でデータ構造の集合setを実現し、チェーンテーブルlistを使用してその下位実装を行うと、listから継承された(is-aを構成し、明らかに一致しない)、listをsetのメンバー変数として使用するべきではありません.メンバー関数はチェーンテーブル上で対応する簡単な操作を行えばsetの機能を実現できるのでsetはlistに基づいて実現される.
条項39:private継承を賢明かつ慎重に使用
class base {
private:
virtual void timer() { std::cout<<"base"<<std::endl; }
};
class derived {
public:
void call() { timer_.timer(); }
private:
class inside_class : public base {
public:
virtual void timer() { std::cout<<"inside_timer"<<std::endl; }
};
inside_class timer_;
// derived base, :
//virtual void timer() { std::cout<<"derived"<<std::endl; }
// derived base, : , , 。
};
int main()
{
derived d;
d.call();
return 0;
}
派生クラスで定義できない虚関数を実現するには(注記の削除に注意):クラスAがクラスを継承する場合、その虚関数を実装するには、メンバークラスに共通して継承され、そのメンバークラスのオブジェクトが含まれているメンバークラスを使用します.メンバークラスで虚関数を書き換え、メンバークラスオブジェクトを介して虚関数を呼び出すことができます.その後、クラスBがクラスAを継承する場合、クラスAは定義できません.クラスAのプライベートオブジェクトの関数であるため、ダミー関数です.
条項40:賢明かつ慎重な多重継承の使用