第一章 基于Policy的Class設(shè)計(jì)

  • 在一個(gè)全功能型接口下實(shí)現(xiàn)所有東西的做法并不好掰读,會(huì)造成大量的智力負(fù)荷并且輕微的不可測的定制行為都會(huì)導(dǎo)致整個(gè)精心制作的庫沒有用處离唐。

  • 程序庫將不同的設(shè)計(jì)分解為各個(gè)小型的class忍疾,每個(gè)class代表特定模式下的解法裁赠,這種方法會(huì)產(chǎn)生大量的設(shè)計(jì)組合問題殿漠,如果再加一個(gè)設(shè)計(jì)選項(xiàng),那么會(huì)產(chǎn)生更多的潛在組合佩捞。

  • 多重繼承并不有助于處理設(shè)計(jì)組合绞幌,其存在下面問題:

  • 關(guān)于技術(shù):目前并沒有一成不變即可套用的代碼,可以在某種受控情況下將繼承而來的類組合起來失尖。僅僅是將被組合的基類結(jié)合在一起并建立一組用來訪問其成員的簡單規(guī)則啊奄,除非情況極為單純,否則結(jié)果難以讓人接受
  • 關(guān)于類型信息:基類并沒有足夠的類型信息來繼續(xù)完成他們的工作掀潮。比如菇夸,由一個(gè)DeepCopy Class來為其派生類做出深層拷貝,但是DeepCopy并不清楚其派生類的類型
  • 關(guān)于狀態(tài)處理:基類實(shí)作之各種行為必須操作相同的數(shù)據(jù)仪吧,這意味著他們必須虛繼承一個(gè)持有該數(shù)據(jù)的基類庄新,由于總是user class繼承l(wèi)ibrary class,這會(huì)使得設(shè)計(jì)更加復(fù)雜且更加沒有彈性
  • 模板是一種很適合"組合各種行為"的機(jī)制薯鼠,因?yàn)樗麄兪且蕾囀褂谜咛峁┑念愋托畔⒉⑶以诰幾g期才產(chǎn)生的代碼择诈。和一般的類不同,模板類可以特化其任何成員函數(shù)出皇,來為設(shè)計(jì)特定的行為時(shí)提供良好的控制粒度羞芍,然而模板類也存在如下問題:
  • 無法特化數(shù)據(jù)成員
  • 成員函數(shù)的特化無法"依理擴(kuò)張":可以對(duì)單一模板參數(shù)的模板類特化其成員函數(shù),卻無法對(duì)使用多個(gè)模板參數(shù)的模板類特例化其個(gè)別成員函數(shù)
template<typename T>
class CTest
{
public:
  void Fun(){};
};
template<> void CTest<char>::Fun(){}               //編譯ok

template<typename T, template U>
class CTest1
{
public:
  void Fun(){};
};
template<typename T> void CTest1<char, T>::Fun(){} //編譯報(bào)錯(cuò)
  • 程序庫設(shè)計(jì)者不能提供多筆缺省值:只能對(duì)每個(gè)成員函數(shù)提供一份缺省作品郊艘,無法對(duì)同一個(gè)成員函數(shù)提供多份缺省作品
  • policies和policy class有助于我們?cè)O(shè)計(jì)出安全荷科、有效率且具有高度彈性的設(shè)計(jì)元素。所謂的policy纱注,是用來定義一個(gè)class或class template的接口畏浆,該接口由下列一項(xiàng)或多項(xiàng)組成:內(nèi)隱型別定義(inner type definition)、成員函數(shù)和成員變量狞贱。實(shí)作出policy者稱為policy class刻获。如果class采用了一個(gè)或多個(gè)policies則稱其為host class

  • 例子:定義一個(gè)policy用以生成對(duì)象:Create Policy是一個(gè)帶有類型T的模板類,必須提供一個(gè)名為Create的函數(shù)給外界調(diào)用瞎嬉,此函數(shù)不接受參數(shù)蝎毡,返回一個(gè)指向新創(chuàng)建的T類型對(duì)象的指針厚柳。以下提供三種做法實(shí)例:

//方法一:
template<typename T>
class CNewCreator
{
public:
    static T* Create() { return new T; }
};

//方法二:
template<typename T>
class CMallocCreator
{
public:
    static T* Create()
    {
        void* pBuff = malloc(sizeof(T));
        if (pBuff)
        {
            return new (pBuff) T;   //定位new 需要包含頭文件 #include <new>
        }
        return nullptr;
    }
};

//方法三:
template<typename T>
class CCloneCreator
{
public:
    CCloneCreator(T* pTem = nullptr) : pValue(pTem){}

public:
    T* Create()
    {
        return pValue ? pValue->Clone() : nullptr;
    }

    T* Get() { return pValue; }
    void Set(T* pTem) { pValue = pTem;}

private:
    T* pValue;
};

如上,任何一個(gè)police都可以有無限份實(shí)現(xiàn)方式顶掉,實(shí)現(xiàn)police的類一般不被單獨(dú)使用草娜,主要用于繼承或內(nèi)含與其他類中。policies接口和一般的類接口不同痒筒,他比較松散宰闰,他是語法導(dǎo)向而非標(biāo)記導(dǎo)向。換句話說簿透,Create明確定義的是"怎樣的語法構(gòu)造其所規(guī)范的類"而非"必須實(shí)現(xiàn)出哪些函數(shù)"移袍。例如Create Policy沒有規(guī)定Create必須是static還是virtual,他只要求class必須定義出Create()且不接受參數(shù)的同時(shí)返回指向新對(duì)象的指針
接下來設(shè)計(jì)一個(gè)類來利用Create Policy:

template<typename T>
class CTest : public T 
{
public:
    void Create();
};

template<typename T>
void CTest<T>::Create()
{
    T::Create();
}

int _tmain(int argc, _TCHAR* argv[])
{
    CTest<CNewCreator<int>> Test;
    Test.Create();  //調(diào)用方法一的Create()

    CTest<CMallocCreator<double>> Test1;
    Test1.Create(); //調(diào)用方法二的Create()
    return 0;
}

看以上代碼老充,當(dāng)Test對(duì)象需要產(chǎn)生一個(gè)特定類型的對(duì)象時(shí)葡盗,可以讓CTest類的使用者自行裝配他需要的機(jī)能,這便是 policy-based class的設(shè)計(jì)主旨

  • 在上述例子中啡浊,當(dāng)CTest的使用者清楚的知道其希望使用的類型時(shí)觅够,使用者每次使用仍需要傳入那個(gè)特定類型則顯得很笨重也很不安全,此時(shí)可以使用Template Template參數(shù)
//這里的U是模板類T的模板參數(shù)無法在CTestInt類中使用巷嚣,可以省略喘先。
//T則是CTestInt的模板參數(shù)
template<template<typename/* U*/> class T>  
class CTestInt : public T<int>
{
public:
    void Create();
};

template<template<typename/* U*/> class T>
void CTestInt<T>::Create()
{
    printf("%s\n", typeid(T).name());   //#include <typeinfo>
    T<int>::Create();
}

int _tmain(int argc, _TCHAR* argv[])
{
    CTestInt<CNewCreator> TestInt;      //此處無需傳入CNewCreator的模板參數(shù)
    TestInt.Create();   //會(huì)調(diào)用方法一的Create 輸出"class CNewCreator"

    return 0;
}
  • 如上述的第三種方法的代碼,CCloneCreator<T>類不僅提供了Create函數(shù)廷粒,還提供了其余的函數(shù)窘拯,這些函數(shù)將在不影響host class類原本功能的情況下自動(dòng)豐富host class的接口,因?yàn)闆Q定哪個(gè)policy被使用是使用者而非程序庫本身坝茎。

  • 大部分情況下host class會(huì)以public繼承從某些policies派生而來涤姊,因此除非policy class定義了一個(gè)虛析構(gòu)函數(shù),否則delete一個(gè)指向policy class的指針會(huì)有問題嗤放。然而如果為policy定義虛析構(gòu)函數(shù)思喊,會(huì)妨礙policy的靜態(tài)連接特性,也會(huì)影響執(zhí)行效率次酌。許多policies并無任何數(shù)據(jù)成員恨课,純粹只規(guī)范行為,一個(gè)虛函數(shù)的加入會(huì)為對(duì)象大小帶來額外開銷和措,所以需要避免虛析構(gòu)函數(shù)庄呈。當(dāng)然當(dāng)host class以protected或private繼承policy class時(shí)可以避免這個(gè)問題蜕煌,但是這樣又會(huì)失去豐富的policies特性派阱。policies應(yīng)該采用的解法是:定義一個(gè)protecte的非虛析構(gòu)函數(shù),那么只有派生而來的類才能摧毀這個(gè)policy對(duì)象斜纪,這樣外界就不可能delete一個(gè)指向policy class的指針贫母。

  • 由于使用了模板文兑,所以host class可以通過不完全具現(xiàn)化而獲得選擇性機(jī)能。如果class template有一個(gè)成員函數(shù)未被用到腺劣,他就不會(huì)被編譯器具體實(shí)現(xiàn)出來绿贞,也不會(huì)被進(jìn)行語法檢驗(yàn)

  • 當(dāng)將policies組合起來時(shí),便是他們最有用的時(shí)候橘原。(備注:原書1.9節(jié)的代碼編譯通不過籍铁,下面的代碼是隨便編的,也體現(xiàn)了類似的思想)趾断。如下代碼拒名,CSmartPtr模板參數(shù)中是一個(gè)T類型而不是T* 類型,在遇到比如非標(biāo)準(zhǔn)指針(如Windows下的句柄)時(shí)也可以靈活使用

#include <cassert>
#include <new>

template
<
    typename T,                         //被指對(duì)象類型
    template<typename> class CCheck,    //提供Check功能
    template<typename> class CAlloc     //提供分配功能
>
class CSmartPtr : public CCheck<T>, public CAlloc<T>
{
public:
    CSmartPtr() : pValue(nullptr){}

public:
    T* operator->()
    {
        CCheck<T>::Check(pValue);
        return pValue;
    }

    void Init()
    {
        pValue = CAlloc<T>::Create();
    }

private:
    T* pValue;
};


template<typename T>
class CNotCheck
{
public:
    static void Check(T*){}
};

template<typename T>
class CCheck
{
public:
    static void Check(T* pTem)
    {
        if (!pTem)
        {
            assert(false);
        }
    }
};


template<typename T>
class CNewCreator
{
public:
    static T* Create() { return new T; }
};

template<typename T>
class CMallocCreator
{
public:
    static T* Create()
    {
        void* pBuff = malloc(sizeof(T));
        if (pBuff)
        {
            return static_cast<T*>(pBuff);
        }
        return nullptr;
    }
};

template<typename T>
class CNotCreator
{
public:
    static T* Create()
    {
        return nullptr;
    }
};

struct STest
{
    STest() : nValue(4096) { printf("STest\n"); }
    int nValue;
};

int _tmain(int argc, _TCHAR* argv[])
{
    CSmartPtr<STest, CCheck, CNewCreator> pTest;
    pTest.Init();                   //輸出"STest"
    auto nTem = pTest->nValue;      //nTem = 4096

    CSmartPtr<STest, CNotCheck, CMallocCreator> pTest1;
    pTest1.Init();                  //并沒有輸出 因?yàn)閙alloc并不調(diào)用構(gòu)造函數(shù)
    auto nTem1 = pTest1->nValue;    //nTem1 = -842150451

    CSmartPtr<STest, CCheck, CNotCreator> pTest2;
    pTest2.Init();
    auto nTem2 = pTest2->nValue;    //引發(fā)中斷
    return 0;
}
  • policies之間彼此轉(zhuǎn)換的各種方法中芋酌,最好又最具有擴(kuò)充性的方法是以policy來控制host class對(duì)象的拷貝和初始化
#include <cstdio>
#include <typeinfo>


template<typename T, template<typename> class CCheck>
class CTest : public CCheck<T>
{
public:
    template<typename T1, template<typename> class C1>
    CTest(const CTest<T1, C1>& Test) : CCheck<T>(Test)
    {
        printf("Type Change\n");

        printf("%s, %s\n", typeid(T).name(), typeid(CCheck).name());
        printf("%s, %s\n", typeid(T1).name(), typeid(C1).name());
    }

    CTest(){}
};


template<typename T>
class CCheckMode_0{};


template<typename T>
class CCheckMode_1
{
public:
    CCheckMode_1(const CTest<T, CCheckMode_0>&)
    {
        printf("Deal Change\n");
    }

    template<typename T1>
    CCheckMode_1(const CTest<T1, CCheckMode_0>& TestC)
    {
        printf("Super Change\n");
    }
};


int main()
{
    CTest<int, CCheckMode_0> T0;
    CTest<int, CCheckMode_1> T1(T0);
    printf("------------------\n");
    CTest<double, CCheckMode_1> T2(T0);

    //CTest<int, CCheckMode_0> T3(T1);  //編譯報(bào)錯(cuò)增显,沒有定義對(duì)應(yīng)的拷貝構(gòu)造函數(shù)

    /*
    輸出:
    Deal Change
    Type Change
    int, class CCheckMode_1
    int, class CCheckMode_0
    ------------------
    Super Change
    Type Change
    double, class CCheckMode_1
    int, class CCheckMode_0
    */

    return 0;
}
  • 建立policy-based class design的最困難部分,便是如何將class正確分解為policies脐帝。一個(gè)準(zhǔn)則就是:將參與class行為的設(shè)計(jì)鑒別出來并命名同云。policies之間最好不要存在依賴關(guān)系,否則就是非正交的堵腹,非正交的policies是不完美的設(shè)計(jì)炸站,將導(dǎo)致host class和policy class的設(shè)計(jì)更加復(fù)雜

  • 設(shè)計(jì)就是一種選擇。大多數(shù)時(shí)候我們的困難并不是找不到解決方案秸滴,而是由太多解決方案武契。大至架構(gòu),小至代碼片段荡含,都需要抉擇咒唆。抉擇是可以組合的,這給設(shè)計(jì)帶來了可怕的多樣性释液。policies機(jī)制有template和多重繼承組成全释。一個(gè)class如果使用了policies,我們稱其為host class误债,那是一個(gè)擁有多個(gè)template(通常是template template 參數(shù))的class template浸船,每一個(gè)參數(shù)代表了一個(gè)policy。host class所有機(jī)能都來自policies寝蹈,運(yùn)作起來就像是一個(gè)聚合了無數(shù)個(gè)policies的容器李命。一個(gè)policies-based class可以組合policies而提供非常多的行為,極有效的使policies稱為對(duì)付設(shè)計(jì)期多樣性的好武器箫老。通過policy class封字,不但可以定制行為,還可以定制結(jié)構(gòu)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阔籽,隨后出現(xiàn)的幾起案子流妻,更是在濱河造成了極大的恐慌,老刑警劉巖笆制,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绅这,死亡現(xiàn)場離奇詭異,居然都是意外死亡在辆,警方通過查閱死者的電腦和手機(jī)证薇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匆篓,“玉大人棕叫,你說我怎么就攤上這事∞壬荆” “怎么了俺泣?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長完残。 經(jīng)常有香客問我伏钠,道長,這世上最難降的妖魔是什么谨设? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任熟掂,我火速辦了婚禮,結(jié)果婚禮上扎拣,老公的妹妹穿的比我還像新娘赴肚。我一直安慰自己,他們只是感情好二蓝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布誉券。 她就那樣靜靜地躺著,像睡著了一般刊愚。 火紅的嫁衣襯著肌膚如雪踊跟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天鸥诽,我揣著相機(jī)與錄音商玫,去河邊找鬼。 笑死牡借,一個(gè)胖子當(dāng)著我的面吹牛拳昌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钠龙,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼炬藤,長吁一口氣:“原來是場噩夢啊……” “哼扁远!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刻像,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎并闲,沒想到半個(gè)月后细睡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帝火,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年溜徙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犀填。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蠢壹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出九巡,到底是詐尸還是另有隱情图贸,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布冕广,位于F島的核電站疏日,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏撒汉。R本人自食惡果不足惜沟优,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睬辐。 院中可真熱鬧挠阁,春花似錦、人聲如沸溯饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丰刊。三九已至坡慌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藻三,已是汗流浹背洪橘。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棵帽,地道東北人熄求。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逗概,于是被迫代替她去往敵國和親弟晚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,516評(píng)論 1 51
  • 重新系統(tǒng)學(xué)習(xí)下C++;但是還是少了好多知識(shí)點(diǎn)卿城;socket枚钓;unix;stl瑟押;boost等搀捷; C++ 教程 | 菜...
    kakukeme閱讀 19,874評(píng)論 0 50
  • C++ 模版 模板是C++支持參數(shù)化多態(tài)的工具,使用模板可以使用戶為類或者函數(shù)聲明一種一般模式多望,使得類中的某些數(shù)據(jù)...
    jorion閱讀 1,872評(píng)論 0 9
  • 把心事綻放出一朵 嫩舟,桃花 。 知道有你和春天要經(jīng)過 怀偷, 我捧著羞紅 家厌,一次又一次的等:只想把你 ,等成春風(fēng)的模樣椎工;...
    bab87461adbd閱讀 958評(píng)論 1 4
  • gradle各版本下載 簡化bintray上傳的工具:novoda 算法珠璣 lintCode 有面試真題饭于,階梯...
    qpan閱讀 264評(píng)論 0 0