现在的位置: 首页 > 综合 > 正文

<学习笔记>Algorithm Library Design 算法库设计in c++ III(设计策略)

2011年12月07日 ⁄ 综合 ⁄ 共 7440字 ⁄ 字号 评论关闭

1.基于策略的类设计(Policy-Based Class Design)

基于策略的设计是一种面向组件的结构设计技巧。它将一个类的功能分解为不同的策略,每个策略可以有不同的实现,被称为策略类。主类或者称为host class,通过将policy class作为它的模版参数来获取函数功能。

image

作为示例,给出一个策略,它的目的是生成对象。下面有两种不同的实现。

template <class>
struct OpNewCreator {
    static T* create() { return new T; }
};

template <class T>
struct MallocCreator {
    static T* create() {
        void* buf = std::malloc(sizeof(T));
        if (!buf) return 0;
        return new(buf) T;
    }
};

 

我们同时有一个host class来使用policy.(个人觉得这样不是常规意义上public 继承的is-a意义了,类似private继承的implemented using)

template <class CreationPolicy = OpNewCreator<Widget> >
class WidgetManager : public CreationPolicy { 
    // ...
};

 

现在用户可以选择,用什么样的方式来生成对象。hwo to create

typedef WidgetManager<> DefaultWidgetManager;
typedef WidgetManager< MallocCreator<Widget> > MallocWidgetManager;

 

考虑到WidgetManager总是操纵Widget类型数据,所以不应该由用户来显示声明Widget,更好的

方式是用模版模版参数template template parameters.这样甚至可以在host class如下面的WidgetManager内部用同样的策略生成其它类型的对象。

// library code
template <template <class> class CreationPolicy = OpNewCreator>
class WidgetManager : public CreationPolicy<Widget> { 
    // ...
    void doSomething() {
        Gadget* q = CreationPolicy<Gadget>().Create();
        // ...
    }
    // ...
};

 

//application code
typedef WidgetManager<MallocCreator> MallocWidgetManager;

 

当前关于模版的模版的实参,有一个极其容易出差错的地方,参见

<<c++ templates>> p107模版的模版实参。 因为该实参本身具有参数,所以该参数必须应该精确匹配它所替换的模版的模版的模版参数本身的参数。

例如

template<typename T, template<typename> class Container>

class Relation;

如果我们使用Relation<int,std::list> relation;就是错误的。

因为虽然我们平常都是用list<int> list<double>之类的,但是list模版类是有两个参数的而不是一个,尽管后一个是有默认值因此我们一般都忽略,但是在这里是认为不匹配的。

template<typename T, typename Allocator = allocator<T>>

class list;  //list 的声明。

要想使得用户能够像上面那样使用Relation,要像下面这样声明Relation。

#include<memory>

template<typename T, typename = std::allocator<T> > class Container>

class Relation;

2.扩展的策略(Enriched Policies)

我们可以再写一个creation policy class,它不仅仅有create函数,同时还提供其它的函数(接口)

template <class T>
struct PrototypeCreator {
    PrototypeCreator(T* p = 0) : prototype(p) {}
    T* create() {
        return prototype ? prototype->clone() : 0;
    }
    T* get_prototype() { return prototype; }
    void set_prototype(T* p) { prototype = p; }
private:
    T* prototype;
};

这个与前面的policy的实现相比与众不同的policy class实现可以和前面的policy实现一起工作为host class提供服务。

typedef WidgetManager<PrototypeCreator> MyWidgetMgr;
// ...
Widget* p = ...;
MyWidgetMgr mgr;
mgr.set_prototype(p);
 
我们可以在host class 内部针对PrototypeCreator提供一个特别的函数
template <template <class> class CreationPolicy = OpNewCreator>
class WidgetManager : public CreationPolicy<Widget> { 
    // ...
    void switch_prototype(Widget* p) {
        CreationPolicy& myPolicy = *this;
        delete myPolicy.get_prototype();
        myPolicy.set_prototype(p);
    }
    // ...
};

由于模版的"lazy" implicit instantiation机制的存在,我们仍然可以用前面的policy如OpNewCreator,尽管OpNewCreator不支持get_prototype,以及set_prototype.

但是没有关系,编译错误仅仅在你试图调用switch_pototype的时候才会发生。

3.组合策略(Combining Policies)

基于策略的设计模式的威力在有多种策略组合的时候显的尤为明显,给程序设计带来极大的灵活性。Loki库里有一个smart pointer类作为host class 使用4种policies.

template <
    class T,
    template <class> class OwnershipPolicy  = RefCounted,
                     class ConversionPolicy = DisallowConversions,
    template <class> class CheckingPolicy   = AssertCheck,
    template <class> class StoragePolicy    = DefaultSPStorage
>
class SmartPtr;
这里每一种策略都对应有多种可能的实现:
  • Ownership policy: DeepCopy, RefCounted, RefCountedMT, COMRefCounted, RefLinked, DestructiveCopy, and NoCopy.
  • Conversion policy: AllowConversion and DisallowConversion.
  • Checking policy: AssertCheck, AssertCheckStrict, RejectNullStatic, RejectNull, RejectNullStrict, and NoCheck.
  • Storage policy: DefaultSPStorage, ArrayStorage, LockedStorage, and HeapStorage.

这里我们有1 + 7 + 2 + 6 + 4 = 20个组件,在一起它们提供了7 * 2 * 6 * 4 = 336种不同的组合。因此有336种不同的smart pointer 类。

而且以后我们可能还会添加新的policy classes用户也可以定制他们自己的policy classes.

4.Named Template Arguments

在SmartPtr,所有的策略都要默认值,因此写SmartPtr<int>就会得到一个默认的配置。如果要改写ownership policy的实现也很容易,SmarPtr<int, NoCopy>

但是如果我们想要对storage polciy采用非默认的实现,那么所有的模版实参都要显式的给出。类似与Python中的函数

def foo(name = ‘abc,’ age = 18, city = ‘LaSa’):

用户可以方便的写出foo(‘def’, 32),也可以写 foo(age = 32) 从而参数为 name = ‘abc’ age = 32  city = ‘LaSa’

我们在C++这里希望用户可以简单的写成下面的形式

SmartPtr<int, StoragePolicy_is<LockedStorage> > sp; //从而表明除了storage policy 采用LockedSotrage实现,其余polcies均采用默认实现配置。

一种实现这种功能的技巧叫做Named Template Arguments。我们以一个简单的BreadSlicer class作为例子,它有3个policy参数。

template < class PolicySetter1 = DefaultSetter,
           class PolicySetter2 = DefaultSetter,
           class PolicySetter2 = DefaultSetter >
class BreadSlicer {
    typedef PolicySelector < PolicySetter1, PolicySetter2,
                             PolicySetter3 >
            Policies;
    // use policies:
    Policies::P1 // ...
    Policies::P2 // ...
    Policies::P3 // ...
};

下面给出具体测试程序吧,原网页上的示例有些问题,这种方法虽然好用,不过实现细节太繁琐不直观了,多层次的继承,多继承,模版参数作为父类,虚拟继承,非类型参数的模版参数,

通通出现了,想出这个方法的人真是太牛了。

#include <iostream>
using namespace std;

struct DefaultPolicy1{
    static void print(){
        cout << "Default Policy1 print" << endl;
    }
};

struct DefaultPolicy2{
    static void print(){
        cout << "Default Policy2 print" << endl;
    }
};

struct DefaultPolicy3{
    static void print(){
        cout << "Default Policy3 print" << endl;
    }
};

struct MyPolicy1{  //for Policy1 we can choose from defualt DefaultPolicy1 and custom MyPolicy1
    static void print() {
        cout << "My Policy1 print" << endl;
    }
};

struct MyPolicy2{
    static void print() {
        cout << "My Policy2 print" << endl;
    }
};

struct MyPolicy3{
    static void print() {
        cout << "My Policy3 print" << endl;
    }
};

struct DefaultPolicies {
    typedef DefaultPolicy1 P1;
    typedef DefaultPolicy2 P2;
    typedef DefaultPolicy3 P3;
};

class DefaultSetter : virtual public DefaultPolicies {};

template <typename Policy>
struct Policy1_is : virtual public DefaultPolicies {
    typedef Policy P1;
};

template <typename Policy>
struct Policy2_is : virtual public DefaultPolicies {
    typedef Policy P2;
};

template <typename Policy>
struct Policy3_is : virtual public DefaultPolicies {
    typedef Policy P3;
};

template <class Base, int D>
struct Discriminator : public Base {};

template < class Setter1, class Setter2, class Setter3 >
class PolicySelector : public Discriminator<Setter1,1>,
                       public Discriminator<Setter2,2>,
                       public Discriminator<Setter3,3>
{};

template < class PolicySetter1 = DefaultSetter,
           class PolicySetter2 = DefaultSetter,
           class PolicySetter3 = DefaultSetter >
class BreadSlicer {
    typedef PolicySelector < PolicySetter1, PolicySetter2,
                             PolicySetter3 >
            Policies;
    public:
    static void print() {
        Policies::P1::print();  //Policy1 print
        Policies::P2::print();  //Policy2 print
        Policies::P3::print();  //Polciy3 print
    }
};

int main()
{
    cout <<  "using default" << endl;
    BreadSlicer<> bs1;
    bs1.print();
    cout << "using custom policy fo Policy2" << endl;
    BreadSlicer<Policy2_is<MyPolicy2> > bs2;
    bs2.print();
    cout << "using custom policy for Policy2 and Policy3" << endl;
    BreadSlicer<Policy3_is<MyPolicy3>, Policy2_is<MyPolicy2> > bs3;
    bs3.print();
    return 1;
}

//result

using default
Default Policy1 print
Default Policy2 print
Default Policy3 print
using custom policy fo Policy2
Default Policy1 print
My Policy2 print
Default Policy3 print
using custom policy for Policy2 and Policy3
Default Policy1 print
My Policy2 print
My Policy3 print

下面解释一下,

template <class Base, int D>
struct Discriminator : public Base {};

template < class Setter1, class Setter2, class Setter3 >
class PolicySelector : public Discriminator<Setter1,1>,
                       public Discriminator<Setter2,2>,
                       public Discriminator<Setter3,3>
{};

 

Discriminator类模版之所以需要int D这个非类型参数,是为了避免PolicySelector重复的继承同一个父类

如public Discriminator<DefualtSetter>。

struct DefaultPolicies {
    typedef DefaultPolicy1 P1;
    typedef DefaultPolicy2 P2;
    typedef DefaultPolicy3 P3;
};

class DefaultSetter : virtual public DefaultPolicies {};

template <typename Policy>
struct Policy1_is : virtual public DefaultPolicies {
    typedef Policy P1;
};

而只所以用virtual public DefaultPoicies是因为虚拟继承避免重复的继承DefaultPoicies中的成员,只有一个DefaultPoicies作为一个虚基类,避免了多继承造成P1,P2的重定义。

Policy2_is<MyPolicy2>会覆盖掉父类对P2的定义,这正是我们想要的。

原文用的是Policy2_is<CustomPolicy>示例,并给出一个示意图。

image

个人感觉这么实现太复杂了,还不如给一个default 的config类,如上面的struct DefaultPolicies ,然后host class用这个做模版参数就好了,用户自己改的话,定义一个自己的config继承default config,

然后修改需要改动的policy 实现,如 typedef MyPolicy2 P2;

 

 
【上篇】
【下篇】

抱歉!评论已关闭.