[C++]継承とオブジェクト向け設計

16230 ワード

C++設計モード(virtual関数を避ける)
本論文では,virtual関数の使用を避けるためにC++のいくつかの特別な設計モードについて論じる.(virtual関数を呼び出すオーバーヘッドが大きいためです.)
私たちが今ゲームを設計し、このゲーム内の人物のために継承システムを設計すると仮定します.
class GameCharacter {
public:
    virtual int healthValue() const;  //          。
    ...
};

これは非常に明らかな設計ですが、次に、このクラスを改善するためにいくつかの設計モデルを紹介します.
Template Method設計モード
このモードはvirtual関数が常にpriavteであることを主張する.
class GameCharacter {
public:
    int healthValue() const {
        ...
        int retVal = doHealthValue();
        ...
        return retVal;
    }
    ...
private:
    virtual int doHealthValue() const {
        ...
    }
};

この設計により、public non-virtualメンバー関数を介してprivate virtual関数を間接的に呼び出すことができ、「non-virtual interface(NVI)手法」と呼ばれます.彼はいわゆるtemplate method設計モデルの独特な表現である.
NVI手法の1つの利点は,虚関数呼び出し前と呼び出し後に,虚関数が実際に動作する前と後に呼び出されることを確保するために,いくつかの相関額操作を付加できることである.つまり、虚関数を機能させるシーンを設定します.
また,NVI手法はderived classがvirtual関数を再定義することを可能にし,「機能をどのように実現するか」の制御能力を与えるが,base classは「関数がいつ呼び出されるか」の権利を保持している.
Strategy設計モード-Function Pointer
この方法は関数ポインタをメンバーにし,健康値の計算アルゴリズムを変更できる.
class GameCharacter; // forward declaration;
// default algorithm
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
    typedef int (*HealthCalcFunc) (const GameCharacter&);
    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {
    }
    int healthValue() const {
        return healthFunc(*this);
    }
    void setFunc(HealthCalcFunc anoFunc) {
        healthFunc = anoFunc;
    {
    ...
private:
    HealthCalcFunc healthFunc;
};

この設計方式はhealthValueのアルゴリズムに弾力性を提供することができ,それによって異なるオブジェクトに異なる計算方法を持つことができ,さらに異なる時期に異なる計算方法を持つことができる.
しかし、この設計モードでは、例えば、計算方法がpublicインタフェースから提供される情報のみで計算できない場合、この問題を解決する方法は「classのカプセル化を弱める」ことであり、特定の情報のアクセス関数を与えることによって、あるいはfriend関数数を設定することによっても問題が発生する可能性がある.
virtual関数を関数ポインタで置き換える利点は,各オブジェクトに単語の計算関数を持つことができ,実行期間中に計算関数を変更することができるが,クラスのパッケージング性の低下を伴うことである.価値があるかどうかは、状況によって決めなければならない.
付言:typedefの使い方
プログラミングでtypedefを使用する目的は一般的に2つあり、1つは変数に覚えやすく意味の明確な新しい名前を与えることであり、もう1つは比較的複雑なタイプ宣言を簡略化することである.
使用方法:typedef existing_type new_type_name ;
最も簡単な使用:
typedef int size; //  size  int  
typedef unsigned int WORD;

配列とポインタ
typedef char Line[81]; 
Line text, secondline; //  Line   char[81]

typedef char* pstr;   //  pstr   char*
pstr str = "abc";
int mystrcmp(pstr, pstr);

関数#カンスウ#
    void printHello(int i);
    typedef void (*pFunc)(int); //  pFunc      int,     void     。
    pFunc temp;
    temp = printHello;
    temp(110);

Strategy設計モード–functionオブジェクト
関数ポインタを使うのは窮屈すぎる.関数オブジェクト、メンバー関数ポインタ、いくつかの安全な暗黙的なタイプ変換など、より大きな操作弾力性を常に望んでいます.関数ポインタを最適化してfunctionコンテナを使用します.
まずfunctionとbindについて紹介します.
function
C++では、呼び出し可能なエンティティには、主に関数、関数ポインタ、関数参照、関数指定のオブジェクトに暗黙的に変換したり、opetator()を実装したオブジェクト(C++98のfunctor)が含まれます.C++0 xには、std::functionオブジェクト、std::functionオブジェクトが新たに追加されました.functionオブジェクトは、C++に既存の呼び出し可能エンティティのタイプに対して安全な小包です(関数ポインタのような呼び出し可能エンティティは、タイプが安全ではないことを知っています).
ほうそうふつうかんすう
int Add(int x, int y) {
    return x + y;
}
function<int (int, int) > f = Add; //           ,         。
int z = f(2, 3);

パッケージ関数オブジェクト
class CxxPrint {  
public:  
    size_t operator()(const char*) { ... }  
};  
CxxPrint CPrint;
function< size_t (const char*) > f;
f = CPrint;
f(char ...);

パッケージクラスのメンバー関数
#include <functional>
class TAdd {
public:
    int Add(int x, int y) {
        return x + y;
    }
};

int main(void) {
    TAdd tAdd;
    function<int (TAdd*, int, int) > f = &TAdd::Add;
    cout << f(&tAdd, 2, 3);
    return 0 ;
}

関数オブジェクトの状態を保存
#include <iostream>
#include <functional>
using namespace std;
class TAdd {
public:
    TAdd() : val(0) {
    }
    int operator() (int i) {
        val += i;
        return val;
    }
    int Sum() const {
        return val;
    }
private:
    int val;
};

int main(void) {
    TAdd tAdd;
    function<int (int) > f1 = tAdd;
    function<int (int) > f2 = tAdd;
    cout << f1(10) << "," << f2(10) << "," <<  tAdd.Sum() << endl;
    function<int (int) > f1_ref = ref(tAdd);
    function<int (int) > f2_ref = ref(tAdd);
    cout << f1(10) << "," << f2(10) << "," <<  tAdd.Sum() << endl;
    return 0 ;
}
/* output: 10,10,0 20,20,0 Program ended with exit code: 0 */
function<int (int) > f1 = tAdd;

この関数オブジェクトは実際にはf 1にコピーされているので、tAddとf 1は2つの異なるオブジェクトです.したがって、それらの間には関連付けられていないので、1つの出力結果は3つの異なるオブジェクトの出力になります.同時にC++はrefとcref関数を提供し、オブジェクトの参照と常参照のパッケージを提供します.これにより、関数オブジェクトの状態を正しく保存できます.
bind
// bind example
#include <iostream> // std::cout
#include <functional> // std::bind

// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}

struct MyPair {
    double a,b;
    double multiply() {return a*b;}
};

int main () {
    using namespace std::placeholders;    // adds visibility of _1, _2, _3,...

    // binding functions:
    auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
    std::cout << fn_five() << '
'
; // 5 auto fn_half = std::bind (my_divide,_1,2); // returns x/2 std::cout << fn_half(10) << '
'
; // 5 auto fn_invert = std::bind (my_divide,_2,_1); // returns y/x std::cout << fn_invert(10,2) << '
'
; // 0.2 auto fn_rounding = std::bind<int> (my_divide,_1,_2); // returns int(x/y) std::cout << fn_rounding(10,3) << '
'
; // 3 MyPair ten_two {10,2}; // binding members: auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply() std::cout << bound_member_fn(ten_two) << '
'
; // 20 auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a std::cout << bound_member_data() << '
'
; // 10 return 0; }

(From cplusplus.com)
autoは自動タイプで、実行時に自動的に対応するタイプに変換されます.
最適化
class GameCharacter; // forward declaration;
// default algorithm
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
    typedef std::function<int (const GameCharacter&) > HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {
    }
    int healthValue() const {
        return healthFunc(*this);
    }
    void setFunc(HealthCalcFunc anoFunc) {
        healthFunc = anoFunc;
    {
    ...
private:
    HealthCalcFunc healthFunc;
};

functionタイプによって生成されたオブジェクトは、この署名式と互換性のある呼び出し可能なものを持つことができます.互換性とは、この呼び出し可能なパラメータが暗黙的にconst GameCharacter&に変換され、その戻りタイプが暗黙的にintに変換されることを意味する.
この設計モードは、「関数アルゴリズムを指定する」ことに驚くべき弾力性をもたらします.
short calcHealth(const GameCharacter&);

struct HealthCalculator {
//     
    int operator()(const GameCharacter&) const {
    ...
    }
};
class GameLevel {
public:
    float health(const GameCharacter&) const;
    ...
};
class EvilBadGuy : public GameCharacter {
    ...
};

EvilBadGuy ebg1(calcHealth); //     
EvilBadGuy ebg2(HealthCalculator()); //       
GameLevel currentLevel;
...
EvilBadGuy edg3(
    std::bind(&GameLevel::health, currentLevel, _1));  //       。
    //        currentLevel    health    edg3  。