C++设计模式类库 Loki介绍与用法

# C++设计模式类库 Loki介绍与用法
Loki是由Andrei编写的一个与《Modern C++ Design》(C++设计新思维)一书配套发行的C++代码库。

它不仅把C++模板的功能发挥到了极致,而且把类似设计模式这样思想层面的东西通过库来提供。

本篇文章介绍如何利用Loki来轻松地实现一些设计模式。
由于Loki使用了大量牛X到爆的模板技巧,对编译器的要求是很苛刻的,官方兼容列表里只列出了VC7.1以上版本及GCC3.4以上版本。如果你象我一样喜欢用C++Builder6或VC6,可以去下载《Modern C++ Design》配套源码,那里面的Loki提供了对其它不兼容编译器的移植代码,只是版本低了一点,有些接口有些差别。
最后,BTW,《Modern C++ Design》确实是一本好书,在这里也顺便推荐一下^_^

Loki的下载地址是[http://sourceforge.net/projects/loki-lib/](http://sourceforge.net/projects/loki-lib/),目前最新版本是Loki 0.1.7,后面的代码都使用这个版本作为测试标准。

#### 编译

Loki库提供了N多种编译途经,你可以直接打开项目文件(VC、Code::Block、Cpp-Dev等IDE)编译,也可以用传统的makefile来make,还可以直接用批处理文件编译。象我这种被IDE惯坏的人,一般都是直接把src目录里的代码加入到项目中了事。

### Singleton模式(单件模式)

Singleton模式确保一个类在系统中只有一个实例。比如一个窗口系统中只能有一个鼠标对象,只有一个屏幕对象,一个剪切板对象...。我们可以用一个全局变量来做这些工作,但它不能防止实例化多个对象。一个更好的办法是让类自身保存它的唯一实例,并且不允许创建其它实例,这就是Singleton模式。

Loki库的SingletonHolder类提供了对Singleton模式的支持

#### 头文件

1. #include

#### 类型定义

1. template<
2. typenameT,
3. template<class>classCreationPolicy = CreateUsingNew,
4. template<class>classLifetimePolicy = DefaultLifetime,
5. template<class,class>classThreadingModel = ::Loki::SingleThreaded,
6. classMutexPolicy = ::Loki::Mutex>
7. classLoki::SingletonHolder;

Loki的类大部分都是基于策略编程的,其中最主要的是CreationPolicy,它决定了怎样生成一个类实例,可选的有:

* template class Alloc> struct CreateUsing; 在分配器分配的内存中生成实例,如
* template struct CreateStatic 生成静态实例
* template struct CreateUsingMalloc 使用malloc申请内存并在其中生成实例
* template struct CreateUsingNew 使用new生成实例(默认)

#### 示例代码

1. classMyClass{
2. public:
3. // 有默认构造
4. MyClass(){;}
5. // 显示自己所在的内存地址,用以区分是否是同一个对象
6. voidShowPtr()
7. {
8. std::cout <<this<< std::endl;
9. }
10. };
11. // 定义Singleton的MyClass
12. typedefLoki::SingletonHolder MyClassSingleton;
13.
14. int_tmain(intargc, _TCHAR* argv[])
15. {
16. // 通过Instance()静态方法取得MyClass实例
17. MyClass& v = MyClassSingleton::Instance();
18. v.ShowPtr();
19. // MyClassSingleton::Instance()总是返回同一个MyClass实例
20. MyClassSingleton::Instance().ShowPtr();
21. return0;
22. }

Loki::SingletonHolder默认的CreationPolicy策略要求类必须有默认构造,如MyClass这样。如果需要包装没有默认构造的类的话,我们就得自定义一个CreationPolicy策略,好在CreationPolicy策略比较简单,先看看Loki中默认的CreateUsingNew吧:

1. template<classT>structCreateUsingNew
2. {
3. staticT* Create()
4. {returnnewT; }
5.
6. staticvoidDestroy(T* p)
7. {deletep; }
8. };

呵呵,简单吧,只是简单的Create和Destroy而已。

我们只要修改Create()静态方法,new一个自己的对象就可以了,当然随便多少构造参数都可以在这里写上去啦。另外,如有必要,也可以做一些其它初始工作哦。

1. classMyClass2{
2. public:
3. // 构造里要求两个整数
4. MyClass2(int,int){;}
5. voidShowPtr()
6. {
7. std::cout <<this<< std::endl;
8. }
9. };
10. // 我们自己的CreationPolicy策略
11. template<classT>
12. classCreateMyClass2UsingNew:
13. publicLoki::CreateUsingNew
14. {
15. public:
16. staticT* Create()
17. {returnnewT(0,0); }
18. };
19. // 定义使用CreateMyClass2UsingNew策略的Singleton类
20. typedefLoki::SingletonHolder
21. CreateMyClass2UsingNew> MyClass2Singleton;
22. // 使用之
23. int_tmain(intargc, _TCHAR* argv[])
24. {
25. MyClass2Singleton::Instance().ShowPtr();
26. MyClass2Singleton::Instance().ShowPtr();
27. return0;
28. }
29.



**usidc5** 2011-01-18 16:48

### 对象工厂 Object Factory

又名简单工厂模式,貌似不属于设计模式范畴。它的作用是把对象的创建工作集中起来,并使创建工作与其它部分解耦。比如下面这个函数也可当作简单工厂:

1. CWinBase* Create(string s)
2. {
3. if(s =="Edit")
4. returnnewCEdit;
5. elseif(s =="Button")
6. returnnewCButton;
7. ...
8. }

Loki库的Factory类提供了对简单工厂模式的支持。

#### 头文件

1. #include

#### 类型

1. template<
2. classAbstractProduct,// “产品”基类型
3. typenameIdentifierType,// 用什么区分各产品
4. typenameCreatorParmTList = NullType,// 生成器参数
5. template<typename,class>classFactoryErrorPolicy = DefaultFactoryError>
6. classLoki::Factory;

#### 成员方法

bool Register(const IdentifierType& id, ProductCreator creator); 以id作为识别码注册生成器。函数、对象方法或仿函数都可以作为生成器。
bool Unregister(const IdentifierType& id); 取消注册
std::vector RegisteredIds(); 取得已注册的所有识别码
AbstractProduct* CreateObject(const IdentifierType& id);

AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1);

AbstractProduct* CreateObject(const IdentifierType& id, Parm1 p1, Parm2 p2);

...
按识别码id生成对象(调用对应的生成器)

#### 示例代码

1. #include
2. #include
3. #include
4. #include
5.
6. // 窗体基类
7. structIWidget{
8. virtualvoidprintName() = 0;
9. virtual~IWidget(){;}
10. };
11. // 定义窗体工厂,使用string区分各对象类型
12. typedefLoki::Factory widget_factory_t;
13. // 按钮窗体
14. structCButton : IWidget{
15. voidprintName()
16. {
17. std::cout <<"CButton"<< std::endl;
18. }
19. };
20. // 编辑框窗体
21. structCEdit : IWidget{
22. voidprintName()
23. {
24. std::cout <<"CEdit"<< std::endl;
25. }
26. };
27. // 列表框窗体
28. structCListBox : IWidget{
29. voidprintName()
30. {
31. std::cout <<"CListBox"<< std::endl;
32. }
33. };
34.
35. int_tmain(intargc, _TCHAR* argv[])
36. {
37. // 工厂实例
38. widget_factory_t wf;
39. // 注册各种窗体的生成器,这里偷懒用了CreateUsingNew作为生成器
40. wf.Register("Edit", Loki::CreateUsingNew::Create );
41. wf.Register("Button", Loki::CreateUsingNew::Create );
42. wf.Register("ListBox", Loki::CreateUsingNew::Create );
43. // 测试,使用工厂生成窗体
44. {
45. IWidget* pWid = wf.CreateObject("Edit");
46. pWid->printName();
47. deletepWid;
48. }
49. {
50. IWidget* pWid = wf.CreateObject("ListBox");
51. pWid->printName();
52. deletepWid;
53. }
54. return0;
55. }

很多时候,工厂往往只需要一个实例,我们可以使用前面说过的SingletonHolder把widget_factory_t弄成Singleton模式。

上面生成CButton之类的窗体时使用的是默认构造,所以我偷懒没写各个类的生成器,直接用了Loki::CreateUsingNew。

如果你的类有构造参数的话,那么就得在Loki::Factory模板参数中指出参数类型,并且自定义生成器,就象这样:

1. #include
2. #include
3. #include
4.
5. // 窗体基类
6. structIWidget{
7. virtualvoidprintName() = 0;
8. virtual~IWidget(){;}
9. };
10. // 定义窗体工厂,这里用Loki::Seq指定参数类型
11. typedefLoki::Factory<
12. IWidget,
13. std::string,
14. Loki::Seq<int,char>
15. > widget_factory_t;
16. // 单件模式,注意Lifetime策略要选择Loki::LongevityLifetime::DieAsSmallObjectChild,否则...
17. typedefLoki::SingletonHolder
18. Loki::LongevityLifetime::DieAsSmallObjectChild> Singleton_Fac;
19. // 按钮窗体
20. structCButton : IWidget{
21. voidprintName()
22. {
23. std::cout <<"CButton:"<< m_txt << std::endl;
24. }
25. CButton(std::string txt,int,char)//多个构造参数
26. :m_txt(txt){}
27. std::string m_txt;
28. };
29. // 编辑框窗体
30. structCEdit : IWidget{
31. voidprintName()
32. {
33. std::cout <<"CEdit:"<< m_txt << std::endl;
34. }
35. CEdit(std::string txt,int,char)//多个构造参数
36. :m_txt(txt){}
37. std::string m_txt;
38. };
39. // 列表框窗体
40. structCListBox : IWidget{
41. voidprintName()
42. {
43. std::cout <<"CListBox:"<< m_txt << std::endl;
44. }
45. CListBox(std::string txt,int,char)//多个构造参数
46. :m_txt(txt){}
47. std::string m_txt;
48. };
49. // 自定义的窗体构造器,用模板只是为了方便点^_^
50. template<classT>structCreateT
51. {
52. T * operator()(std::string txt,intp1,charp2)const
53. {
54. returnnewT(txt, p1, p2);
55. }
56. };
57.
58. int_tmain(intargc, _TCHAR* argv[])
59. {
60. // 工厂实例
61. widget_factory_t& wf = Singleton_Fac::Instance();
62. // 注册各种窗体的生成器,用我们的生成器
63.
64. wf.Register("Edit", CreateT() );
65. wf.Register("Button", CreateT() );
66. wf.Register("ListBox", CreateT() );
67.
68. // 测试,使用工厂生成窗体
69. {
70. IWidget* pWid = wf.CreateObject("Edit","Hello", 0,' ');
71. pWid->printName();
72. deletepWid;
73. }
74. {
75. IWidget* pWid = wf.CreateObject("ListBox","World", 0,' ');
76. pWid->printName();
77. deletepWid;
78. }
79.
80. return0;
81. }

Loki::Seq是一个类似于TypeList的东东,可以存放一系列的类型。另外用SingletonHolder包装Factory时,一定要用Loki::LongevityLifetime::DieAsSmallObjectChild作为SingletonHolder的lifttime策略(Loki使用说明上说的,由于Factory使用了Loki内部的内存管理器SmallObject)。



**usidc5** 2011-01-18 16:49

### Abstract Factory模式(抽象工厂)

抽象工厂提供了一个创建一系列相关或相互依赖对象的接口,而无需指定具体的类。

比如要编写一个可换肤的用户界面,那么我们生成的每个窗体都应该遵循统一的风格,不应该在一个界面里同时出现Mac风格和Win风格的按钮。为了便于控制,我们可以这样写代码:

1. structIWidgetFactory{
2. virtualIButton* CreateButton() = 0;
3. virtualIEdit* CreateEdit() = 0;
4. virtualIListBox* CreateListBox() = 0;
5. };
6. structCWindowsFactory : IWidgetFactory{
7. virtualIButton* CreateButton(){生成Win风格按钮;}
8. virtualIEdit* CreateEdit(){生成Win风格编辑框;}
9. virtualIListBox* CreateListBox(){生成Win风格列表框;}
10. };
11. structCMacFactory : IWidgetFactory{
12. virtualIButton* CreateButton(){生成Mac风格按钮;}
13. virtualIEdit* CreateEdit(){生成Mac风格编辑框;}
14. virtualIListBox* CreateListBox(){生成Mac风格列表框;}
15. };

这样,在程序中我们要用执有IWidgetFactory指针,在必要时实例化某个具体风格的工厂类,最后所有的窗体都由这个IWidgetFactory指针来生成即可。这就是Abstract Factory模式。Loki库的AbstractFactory和ConcreteFactory提供了对Abstract Factory模式的支持。

#### 头文件

1. #include

#### 类型

template
<
    class TList,
    template <class>
        class Unit = AbstractFactoryUnit
>
class AbstractFactory;
AbstractFactory模板类是一个虚类,它根据TList中的类型提供一组Create<>()方法。

* 模板参数TList是一个Typelist,输入所有工厂可生产的产品基类,如前面的IButton,IEdit,IListBox。
* 模板参数Unit依据TList中的所有类型产生对应的Create<>()虚函数,一般直接用默认的AbstractFactoryUnit就可以了。

template<    class AbstractFact,    template <class, class>        class Creator = OpNewFactoryUnit,    class TList = typename AbstractFact::ProductList>class ConcreteFactory;
ConcreteFactory实现了AbstractFactory中的Create<>()方法

* 模板参数AbstractFact就是对应的AbstractFactory类
* 模板参数Creator是产品实例的生成策略,可选的有OpNewFactoryUnit和PrototypeFactoryUnit。
* OpNewFactoryUnit使用new生成新的产品实例;
* PrototypeFactoryUnit使用已有产品(原型)的Clone()方法生成新的实例,这也意味着要使用PrototypeFactoryUnit我们的产品基类必须要有T *Clone()成员方法。

* TList提供一组具体的产品类型,如果Creator策略是PrototypeFactoryUnit,可以不提供。

#### 演示代码

1. #include
2. #include
3. #include
4. // 产品基类
5. structIButton{
6. virtualvoidclick() = 0;
7. };
8. structIEdit{
9. virtualvoidedit() = 0;
10. };
11. structIListBox{
12. virtualvoidscroll() = 0;
13. };
14. // 抽象工厂
15. typedefLoki::AbstractFactory<
16. LOKI_TYPELIST_3(IButton, IEdit, IListBox)//也可以用Loki::Seq< IButton, IEdit, IListBox >::Type
17. > IWidgetFactory;
18.
19. // 具体产品-Win
20. structCWinBtn : IButton{
21. virtualvoidclick(){
22. std::cout<<"CWinBtn Clicked"<< std::endl;
23. }
24. };
25. structCWinEdit : IEdit{
26. virtualvoidedit(){
27. std::cout<<"CWinEdit Editing"<< std::endl;
28. }
29. };
30. structCWinLB : IListBox{
31. virtualvoidscroll(){
32. std::cout<<"CWinLB Scrolling"<< std::endl;
33. }
34. };
35. // 具体产品-Mac
36. structCMacBtn : IButton{
37. virtualvoidclick(){
38. std::cout<<"CMacBtn Clicked"<< std::endl;
39. }
40. };
41. structCMacEdit : IEdit{
42. virtualvoidedit(){
43. std::cout<<"CMacEdit Editing"<< std::endl;
44. }
45. };
46. structCMacLB : IListBox{
47. virtualvoidscroll(){
48. std::cout<<"CMacLB Scrolling"<< std::endl;
49. }
50. };
51. // 具体工厂
52. typedefLoki::ConcreteFactory<
53. IWidgetFactory,
54. Loki::OpNewFactoryUnit,
55. LOKI_TYPELIST_3(CWinBtn, CWinEdit, CWinLB)
56. > CWindowsFactory;
57.
58. typedefLoki::ConcreteFactory<
59. IWidgetFactory,
60. Loki::OpNewFactoryUnit,
61. LOKI_TYPELIST_3(CMacBtn, CMacEdit, CMacLB)
62. > CMacFactory;
63.
64. // 使用工厂生成的产品
65. voidUsingWidget(IWidgetFactory *pFac)
66. {
67. IEdit* pEdt = pFac->Create();
68. IButton* pBtn = pFac->Create();
69. IListBox* pLB = pFac->Create();
70. pEdt->edit();
71. pBtn->click();
72. pLB->scroll();
73. deletepEdt;
74. deletepBtn;
75. deletepLB;
76. }
77.
78. int_tmain(intargc, _TCHAR* argv[])
79. {
80. // 使用Win风格
81. {
82. CWindowsFactory winfac;
83. UsingWidget(&winfac);
84. }
85.
86. // 使用Mac风格
87. {
88. CMacFactory macfac;
89. UsingWidget(&macfac);
90. }
91. return0;
92. }
93.



**usidc5** 2011-01-18 16:49

### Visitor 模式(访问者模式)

访问者模式把对某对象结构中的各元素的相关操作集中到一起,可以方便地在不改变无素类的前提下添加新的操作。

假设一个XML类,我们可能要打印、保存、转换这个XML数据。实际上这些操作都需要遍历节点,不同的只是操作的方式,我们可以这样写代码:

1. structIVisitor;
2. structTTextNode{
3. virtualvoidaccept(IVisitor *visitor){ visitor->visit(this); }
4. };
5. structTCDataNode{
6. virtualvoidaccept(IVisitor *visitor){ visitor->visit(this); }
7. };
8. structTElementNode{
9. virtualvoidaccept(IVisitor *visitor){ visitor->visit(this); }
10. };
11. structIVisitor{
12. virtualvoidvisit(TTextNode*) = 0;
13. virtualvoidvisit(TCDataNode*) = 0;
14. virtualvoidvisit(TElementNode*) = 0;
15. };
16.
17. structCSaveVisitor{
18. virtualvoidvisit(TTextNode*){ 保存文本内容 }
19. virtualvoidvisit(TCDataNode*){ 保存[[CData内容]] }
20. virtualvoidvisit(TElementNode*){ 保存<标签> 所有子节点.accept(this) }
21. };
22.
23. structCPrintVisitor{
24. ...
25. };

当然,这种模式的问题是可能会破坏一些封装性。我们这里不讨论Visitor模式的优劣,先看看Loki是怎么实现这个模式的吧

#### 头文件

1. #include

#### 类型

template
<
    typename R = void,
    template <typename, class> class CatchAll = DefaultCatchAll,
    bool ConstVisitable = false
>
class BaseVisitable;
可访问类型的基类,如果要让元素能被访问,要必须继承自BaseVisitable。


* 模板参数R是Accept()方法的返回值。
* 模板参数CatchAll表示某元素没有对应的访问方法时的反应,默认的DefaultCatchAll是什么也不做。
* 模板参数ConstVisitable表示Accept()方法是否是const方法。
class BaseVisitor;
所有访问者的基类
template <
  class T,
  typename R = void,
  bool ConstVisit = false>
class Visitor;
访问者实现模板


* 模板参数T是一个Typelist,表示访问者能接受的所有元素类型
* 模板参数R是Visit()方法的返回值
* 模板参数ConstVisit表示Visit()方法是否是const方法。

#### 示例代码

1. #include
2. #include
3. #include
4. #include
5. #include
6. // XML元素
7. structTTextNode : Loki::BaseVisitable<>{
8. LOKI_DEFINE_VISITABLE();
9. TTextNode(std::string text)
10. :m_text(text){}
11. std::string m_text;
12. };
13. structTCDataNode : Loki::BaseVisitable<>{
14. LOKI_DEFINE_VISITABLE();
15. TCDataNode(std::string text)
16. :m_cdata(text){}
17. std::string m_cdata;
18. };
19. structTElementNode : Loki::BaseVisitable<>{
20. LOKI_DEFINE_VISITABLE();
21.
22. std::string m_name;
23. TElementNode(std::string text)
24. :m_name(text){}
25. typedefLoki::BaseVisitable<> visitable_t;
26. //这里用了Loki::SmartPtr,这是一个智能指针类型
27. typedefLoki::SmartPtr<
28. Loki::BaseVisitable<>
29. > ptr_t;
30. std::vector< ptr_t > m_childs;
31. };
32. // 访问者,必须继承自BaseVisitor
33. classCPrintVisitor :
34. publicLoki::BaseVisitor,
35. publicLoki::Visitor
36. {
37. public:
38. voidVisit(TTextNode& n){std::cout << n.m_text;}
39. voidVisit(TCDataNode& n){std::cout <<"<< n.m_cdata <<"]]>";}
40. voidVisit(TElementNode& n){
41. std::cout<
42. std::cout<<'<'<< n.m_name <<'>'<< std::endl;
43. for(size_tidx=0, len=n.m_childs.size(); idx
44. {
45. n.m_childs[idx]->Accept(*this);
46. }
47. std::cout<< std::endl <<"<< n.m_name <<'>'<< std::endl;
48. }
49. };
50.
51. intmain()
52. {
53. // 构建XML结构
54. TElementNode root("root");
55. TElementNode *child1 =newTElementNode("child1");
56. child1->m_childs.push_back(newTTextNode("hello"));
57. TElementNode *child2 =newTElementNode("child2");
58. child2->m_childs.push_back(newTCDataNode("world >_<"));
59. root.m_childs.push_back( child1 );
60. root.m_childs.push_back( child2 );
61.
62. // 用CPrintVisitor访问XML所有元素
63. CPrintVisitor visitor;
64. root.Accept(visitor);
65. }

#### 输出结果

hello



_<]]>





**usidc5** 2011-01-18 16:51


大牛Andrei Alexandrescu的《Modern C++ Design》讨论的是C++语言的最前沿研究:generative programming。本书中译版估计得要半年以后才能出来,所以只能靠其所附源码来窥测generative programming了。

目前,我刚将源码读解了约一半,等全部读完,我会将我的读解注释放出来的。现在,现谈一下我的感想。

先扯得远一点。C++有两个巨大优点:和C兼容,自由;有两个巨大缺点:和C兼容,复杂。C++极其复杂,很难掌握,而这正是“自由”的代价。C++语言是个多编程风格的语言,它同时支持过程化、基于对象、面向对象、泛型、生成性这5种编程思想,具有极其强大的表达能力,可以方便地将各种设计转化为实现。

generic Programming的思想精髓是基于接口编程(相对于OOP,连多态所需的基类都不要了),它的技术出发点是选择子,核心技术是:类型推导、类型萃取、特化/偏特化,其成果是STL库:一组通用容器和一组操作于通用容器上的通用算法。

generative programming的思想精髓是基于策略编程(编译器根据策略自动生成所需代码,由于具有更高的抽象性,所以代码复用度也更高),在Loki库的实现中,目前只使用了递归策略,它的技术出发点是Typelist,核心技术是:类型推导、类型萃取、特化/偏特化、多重继承、类型间去耦合,其成果是Loki库:对设计模式的封装。

Typelist是一种对类型本身进行存储和管理的技巧,它的源码已经贴过了,我也作了注解,此处不再谈论。

这是多重继承在COM之后的又一大型运用。多重继承极易发生菱型缺陷,所以Loki库使用了类型间去耦合技术来避免:

template

struct Type2Type

{

typedef T OriginalType;

};

经过这样一层转换后,原类型T间的各种转换关系(尤其是继承/派生关系)已不复存在,菱型缺陷不会再发生了。

Loki库的具体实现相当讲究技巧,设计它非常困难(难度远大于STL库,和Boost库有得一拼啊)。但使用它却非常容易,而且便利显著。由于Loki库提供了对设计模式的封装,所以极大量地丰富了C++语言的表达能力,使的你的设计更容易地转化为实现。

目前,Loki库只提供了对厂模式和visitor模式的封装,它还处于发展初期。


来源: [http://blog.csdn.net/chollima/article/details/8158580](http://blog.csdn.net/chollima/article/details/8158580)




来自为知笔记(Wiz)



原文链接: https://www.cnblogs.com/nfking/p/5812239.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/239489

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午6:10
下一篇 2023年2月13日 下午6:10

相关推荐