在一個(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)