当我们要提供可复用,高度灵活性的组件、类库等情况时,我们该如何着手?policy classes是一种重要的类的设计技术,能大大提高程序的弹性和可复用性,也是《C++设计新思维》作者Andrei Alexandrescu所推崇的技术。

策略policy的基本想法
是把需要实现的类的一些相对独立的行为(behavior)提取出来,然后抽象出每个行为对应的应具备的接口,这就是一个policy,而你可以针对这个policy提供任意个版本的policy classes,并在使用时根据情况选择一个最合适的policy class作为真正使用的版本。

这种通过组合各种policies,得到的宿主类(host class)具有非常大的灵活性,而且每个host可以使用的policy class的数量以及实现一个policy的实现方式也不限。进一步来说,通过使用模板来实现,不仅能在编译器完成很多约束条件(traits生成类型,静态断言作为约束),而且没有任何运行时(run-time)开销。

将一个host怎么分解提取出合理的policies是最困难的问题,一个准则是:

关于使用到的技术要点的说明

多重继承

有时候所定义的policy class的成员函数需要被使用,并且不适合作为组合的方式(创建成员)来实现时,这时候需要用多重继承。需要注意的是,我们设计的泛型的host类,虽然从policy classes继承,但是它们不会被期望这样使用:使用host的指针或者引用,而实际却指向某个policy class。在这个前提下,我们的继承仅仅是使用基类的行为,而且析构函数也不用设计为virtual的,host的析构函数只用关心自己的成员的析构。

例如:

// Loki库中的SmartPtr声明:
template
< 
    typename T,
    class OwnershipPolicy,
    class ConversionPolicy,
    class CheckingPolicy,
    class StoragePolicy
> 
class SmartPtr
    : public StoragePolicy::In<T>::type
    , public OwnershipPolicy::In<typename StoragePolicy::template PointerType<T>::type>::type
    , public CheckingPolicy ::In<typename StoragePolicy::template StoredType<T>::type>::type
    , public ConversionPolicy
{ };

template template parameters 模板的模板参数

模板的模板参数,让policy classes技术更简洁。

一般来说,policy本身也是模板,它操作的对象类型与host操作的类型常常是一致的,这意味着我们常常会这么使用:host< T, Policy1<T>, Policy2<T>… >,尽管默认模板参数可以让我们在大多数情况下不用指定policy而可以简单的使用host类,但是这个冗余的T本可以省略的,因为我们期望这样来写,host< T, Policy1, Policy2… >,template template parameters带给了我们这种能力。

例子:

假设vector, list, deque等的模板参数为template<T, Alloc>,并且Alloc默认为stdL::allocater<T>,容器适配器stack使用时,需要stack< int, vector<int> >,即int被指定了两次,但是照道理来说,我们需要存储的确实就是int型的,没必要写两次。

template
< 
    typename T,
    template<typename,typename> class Sequence = std::deque // 也可以提供默认
> 
class stack
{
public:
    // VS中的STL的vector,list,deque是这样的格式
    typedef Sequence<T,std::allocator<T> > Seq;
    typedef typename Seq::value_type value_type;
    typedef typename Seq::size_type size_type;
    typedef typename Seq::reference reference;
    typedef typename Seq::const_reference const_reference;
    
protected:
    Seq c;
    
public:
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference top() { return c.back(); }
    const_reference top() const { return c.back(); }
    void push(const value_type& x) { c.push_back(x); }
    void pop() { c.pop_back(); }
};

对于这个自定义的stack,我们就只需要一次指定类型了:stack< int, std::vector >就可以了。

使用policy classes的几种常用情况

host类使用policy类的静态成员(函数)

Loki的Singleton也是这种方式实现的,它组合了对象创建(ObjectCreate),生命期(LifeTime),多线程(ThreadingModel)支持三个policy。

假设情况:Policy需要得到一个指定类型的对象的指针,接口Create返回指针,Delete对应的进行删除。

// 一种实现:
#include <iostream>
using namespace std;
 
template< typename T >
struct OpNewCreator
{
    static T* Create()
    {
       cout << "new" << endl;
       return new T;
    }
    static void Delete( T* p )
    {
       cout << "delete" << endl;
       delete p;
    }
};
 
template< typename T >
struct MallocCreator
{
    static T* Create()
    {
       cout << "malloc" << endl;
       void* buf = std::malloc( sizeof(T) );
       return buf==0 ? 0 : (new(buf) T);
    }
    static void Delete( T* p )
    {
       cout << "free" << endl;
       std::free( (void*)p );
    }
};
 
template
< 
    typename T,
    template< typename > class CreatePolicy = OpNewCreator
> 
class MyClass
{
    T* pdata;
public:
    MyClass() : pdata( CreatePolicy<T>::Create() )
    {}
    ~MyClass()
    {
       CreatePolicy<T>::Delete(pdata);
    }
 
};
 
int main()
{
    MyClass<int>();
    MyClass<int, MallocCreator>();
    return 0;
}

输出:
new
delete
malloc
free

policy扮演组合关系

policy类对象作为host类的一个成员,比如上面的stack的例子,我们需要一个存储器作为成员,由于存储器是可选择的,因此作为一个policy。

policy classes作为host类的基类

policy classes作为host类的基类,是因为我们需要使用policy的成员函数。因为每个policy class都是细度很小并且对应一个独立的行为,因此,policy classes相互之间无影响;而且不存在虚函数,于是policy class集中注意力实现需要实现的接口,而host类只管调用policy提供的接口来完成自己的事情,析构函数不需要是虚函数,因为不会设计到动态类型识别,也不需要,因为类型信息对应模板来说都是编译期已知的。比如Loki中的SmartPtr的定义。

使用policy总结

很多时候policy class还有自己的自定义类型,数据成员,静态成员,普通成员函数之中的一些,我们需要根据情况选择是使用: