第一章 基于Policy的Class设计
-
在一个全功能型接口下实现所有东西的做法并不好,会造成大量的智力负荷并且轻微的不可测的定制行为都会导致整个精心制作的库没有用处。
-
程序库将不同的设计分解为各个小型的class,每个class代表特定模式下的解法,这种方法会产生大量的设计组合问题,如果再加一个设计选项,那么会产生更多的潜在组合。
-
多重继承并不有助于处理设计组合,其存在下面问题:
- 关于技术:目前并没有一成不变即可套用的代码,可以在某种受控情况下将继承而来的类组合起来。仅仅是将被组合的基类结合在一起并建立一组用来访问其成员的简单规则,除非情况极为单纯,否则结果难以让人接受
- 关于类型信息:基类并没有足够的类型信息来继续完成他们的工作。比如,由一个DeepCopy Class来为其派生类做出深层拷贝,但是DeepCopy并不清楚其派生类的类型
- 关于状态处理:基类实作之各种行为必须操作相同的数据,这意味着他们必须虚继承一个持有该数据的基类,由于总是user class继承library class,这会使得设计更加复杂且更加没有弹性
- 模板是一种很适合"组合各种行为"的机制,因为他们是依赖使用者提供的类型信息并且在编译期才产生的代码。和一般的类不同,模板类可以特化其任何成员函数,来为设计特定的行为时提供良好的控制粒度,然而模板类也存在如下问题:
- 无法特化数据成员
- 成员函数的特化无法"依理扩张":可以对单一模板参数的模板类特化其成员函数,却无法对使用多个模板参数的模板类特例化其个别成员函数
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(){} //编译报错
- 程序库设计者不能提供多笔缺省值:只能对每个成员函数提供一份缺省作品,无法对同一个成员函数提供多份缺省作品
-
policies和policy class有助于我们设计出安全、有效率且具有高度弹性的设计元素。所谓的policy,是用来定义一个class或class template的接口,该接口由下列一项或多项组成:内隐型别定义(inner type definition)、成员函数和成员变量。实作出policy者称为policy class。如果class采用了一个或多个policies则称其为host class
-
例子:定义一个policy用以生成对象:Create Policy是一个带有类型T的模板类,必须提供一个名为Create的函数给外界调用,此函数不接受参数,返回一个指向新创建的T类型对象的指针。以下提供三种做法实例:
//方法一:
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;
};
如上,任何一个police都可以有无限份实现方式,实现police的类一般不被单独使用,主要用于继承或内含与其他类中。policies接口和一般的类接口不同,他比较松散,他是语法导向而非标记导向。换句话说,Create明确定义的是"怎样的语法构造其所规范的类"而非"必须实现出哪些函数"。例如Create Policy没有规定Create必须是static还是virtual,他只要求class必须定义出Create()且不接受参数的同时返回指向新对象的指针
接下来设计一个类来利用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(); //调用方法一的Create()
CTest<CMallocCreator<double>> Test1;
Test1.Create(); //调用方法二的Create()
return 0;
}
看以上代码,当Test对象需要产生一个特定类型的对象时,可以让CTest类的使用者自行装配他需要的机能,这便是 policy-based class的设计主旨
- 在上述例子中,当CTest的使用者清楚的知道其希望使用的类型时,使用者每次使用仍需要传入那个特定类型则显得很笨重也很不安全,此时可以使用Template Template参数
//这里的U是模板类T的模板参数无法在CTestInt类中使用,可以省略。
//T则是CTestInt的模板参数
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的模板参数
TestInt.Create(); //会调用方法一的Create 输出"class CNewCreator"
return 0;
}
-
如上述的第三种方法的代码,CCloneCreator<T>类不仅提供了Create函数,还提供了其余的函数,这些函数将在不影响host class类原本功能的情况下自动丰富host class的接口,因为决定哪个policy被使用是使用者而非程序库本身。
-
大部分情况下host class会以public继承从某些policies派生而来,因此除非policy class定义了一个虚析构函数,否则delete一个指向policy class的指针会有问题。然而如果为policy定义虚析构函数,会妨碍policy的静态连接特性,也会影响执行效率。许多policies并无任何数据成员,纯粹只规范行为,一个虚函数的加入会为对象大小带来额外开销,所以需要避免虚析构函数。当然当host class以protected或private继承policy class时可以避免这个问题,但是这样又会失去丰富的policies特性。policies应该采用的解法是:定义一个protecte的非虚析构函数,那么只有派生而来的类才能摧毁这个policy对象,这样外界就不可能delete一个指向policy class的指针。
-
由于使用了模板,所以host class可以通过不完全具现化而获得选择性机能。如果class template有一个成员函数未被用到,他就不会被编译器具体实现出来,也不会被进行语法检验
-
当将policies组合起来时,便是他们最有用的时候。(备注:原书1.9节的代码编译通不过,下面的代码是随便编的,也体现了类似的思想)。如下代码,CSmartPtr模板参数中是一个T类型而不是T* 类型,在遇到比如非标准指针(如Windows下的句柄)时也可以灵活使用
#include <cassert>
#include <new>
template
<
typename T, //被指对象类型
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(); //并没有输出 因为malloc并不调用构造函数
auto nTem1 = pTest1->nValue; //nTem1 = -842150451
CSmartPtr<STest, CCheck, CNotCreator> pTest2;
pTest2.Init();
auto nTem2 = pTest2->nValue; //引发中断
return 0;
}
- policies之间彼此转换的各种方法中,最好又最具有扩充性的方法是以policy来控制host class对象的拷贝和初始化
#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); //编译报错,没有定义对应的拷贝构造函数
/*
输出:
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。一个准则就是:将参与class行为的设计鉴别出来并命名。policies之间最好不要存在依赖关系,否则就是非正交的,非正交的policies是不完美的设计,将导致host class和policy class的设计更加复杂
-
设计就是一种选择。大多数时候我们的困难并不是找不到解决方案,而是由太多解决方案。大至架构,小至代码片段,都需要抉择。抉择是可以组合的,这给设计带来了可怕的多样性。policies机制有template和多重继承组成。一个class如果使用了policies,我们称其为host class,那是一个拥有多个template(通常是template template 参数)的class template,每一个参数代表了一个policy。host class所有机能都来自policies,运作起来就像是一个聚合了无数个policies的容器。一个policies-based class可以组合policies而提供非常多的行为,极有效的使policies称为对付设计期多样性的好武器。通过policy class,不但可以定制行为,还可以定制结构