策略思维第一章,c++设计新思维pdf
智湖现代C设计札记(一):政策与技术
第一,软件设计的多样性;第二,全功能界面失效;第三,多继承是救世主吗?四、模板带来曙光五、和策略类示例1.5.1使用模板模板参数实现策略类目的和好处模板默认实参比较虚函数1.5.2使用模板成员函数实现策略类六、丰富策略七。策略类XVIII的析构函数解决方案。通过不完全物化获得的选择机制。组合策略第九课。使用策略分类XI定制结构。战略十二的兼容性。把课堂分成一堆策略总结前言一、软件设计的多样性
设计的多样性不断迷惑新手。当遇到一个软件设计问题时,最好的解决方案是什么?是事件吗?还是物件?观察者?
复试?虚拟?模板?根据不同的尺度和层次,很多不同的解决方案看起来都一样好。
专业软件设计师和新手最大的区别在于,前者知道什么能有效工作,什么不能。任何结构设计问题都有许多合适的解决方案,但它们有不同的规范和优缺点,可能适合也可能不适合眼前的问题。白板上可接受的方案不一定有实用价值。
如何在库中封装灵活且设计良好的组件?
用户如何自己组装这些组件?
如何在合理大小的代码中对抗“邪恶的”多样性?
第二,全功能接口的失败“在全功能接口下实现一切”不是一个好的实践,原因有很多:
更重要的负面影响包括智力负荷、规模急剧攀升和效率考虑。过度丰富的接口的最大问题可能是缺乏静态类型安全性。
系统架构的一个主要基本原则是:要“设计”一些“公理”,比如你不能产生两个单体对象(第六章)或者一个“不相交”的家族对象(第九章)。理想情况下,一个好的设计应该在编译时强制显示大多数约束(约束、规范)。
在大型接口中存在语法有效但语义无效的问题,只有部分子集可以保持其有效的语义。
三、多传承是救世主?分析多重继承失败的原因:
关于技术(力学)。只需将组合的基类组合在一起,并建立一组简单的规则来访问它们的成员。除非情况极其简单,否则结果不可接受。大多数时候,您必须仔细协调继承类的操作,以便它们能够获得所需的行为。类型信息(类型信息)。基类没有足够的类型信息来继续工作。例如,假设您试图通过继承deep copy类来制作智能指针的深层副本。但是DeepCopy应该有什么样的接口呢?它必须产生未知类型的一对一对象。关于国家操纵。基类实现的每个行为必须操作相同的状态。这意味着它们必须虚拟地一一继承保存状态的基类。因为用户类总是继承库类(而不是相反),这将使设计更加复杂和不灵活。虽然本质上是组合的,但多重遗传并不能单独解决设计中的多样性选择。
这里我想到了java只能单向继承的原因,并介绍了接口Interface允许多重继承。继承是面向对象的属性,是类之间的关系,是一组属性的继承。接口是行为的抽象,是某些属性的抽象,是某些能力的定义。
第四,模板带来曙光
模板是一种适合“组合各种行为”的机制,主要是因为它们是“依赖于用户提供的类型信息”和“在编译时生成”的代码。
与一般的类不同,类模板可以用不同的方式定制。如果您想为特定的情况设计一个类,您可以在您的类模板中专门化它的成员函数。例如,如果有一个SmartPrt,您可以将它的任何成员函数专门化为SmartPtr,这可以在设计特定行为时为您提供良好的粒度。
对于那些已经进阶的人,你可以对有多个参数的类模板使用部分模板专门化(第2章介绍的部分专门化)。它允许你根据一些参数专门化一个类模板。
专门化:定义模板时,只使用一些模板参数,其余参数是固定的。
例如,T1固定为char,需要指定T2。
typename 2//部分专门化
类别测试字符,T2
{
公共:
Test(char i,T2 j):a(i),b(j){ cout partial specialization endl;}
私人:
char a;
T2 b;
};
Test char,bool t3(A ,true);//打印部分专门化
完全专门化:定义模板时,模板参数列表为空,使用指定类型的参数。
比如模板参数为空,类参数列表分别指定为int int,char。
模板//完全专业化
类测试int,char
{
公共:
Test(int i,char j) : a(i),b(j)
{
Cout“完全专业化”endl
}
私人:
int a;
char b;
};
Test int,char t2(1, A );//打印完全专门化
另外,函数模板只能完全专门化,不能部分专门化。
例如,下面是一个模板定义:
模板类T.class U class SmartPtr {.};您可以使SmartPtr T,U专用于小部件和其他任意类型,定义如下:
模板类U类SmartPtr小部件,U {.};模板的编译时特性和它的“可组合”特性使它在设计时非常有吸引力。然而,一旦你开始尝试实现这些设计,你会遇到一些不那么简单的问题:
你不能特殊化结构。单独使用模板,无法专门化“类的结构”(我指的是它的几个成员),只能专门化它的成员函数。成员的专业化不能“以理扩人”。可以专门化“单模板参数”的类模板的成员函数,但不能专门化“多模板参数”的类模板的单个成员函数。例如:
模板类T类小部件
无效运行(){.通用实现.}
模板小部件char: fun ()//Translation:原文缺失void。
.专业化实施.
模板类T,类u类小工具
无效运行(){.通用实现.}
//错误!无法部分专门化小工具的成员类
//翻译注意:因为这是成员函数的显式专门化,所以没有分部
//特殊化机制。注意,这不同于类模板:参见c初级读本,第16.9节。
模板类U void小工具char,U :Fun(
.专业化实施.
库编写器不能提供多个默认值。理想情况下,类模板的作者可以为每个成员函数提供一个默认真实作品,但不能为同一个成员函数提供多个默认真实作品。现在让我们比较多重继承的缺点和模板的问题。
有趣的是,这两者相辅相成:
继承多个力学),模板技术丰富。多重继承缺少类型信息,那个东西在模板中大量存在。模板的专门化是不可伸缩的,但是多重继承是可以很容易伸缩的。您只能编写模板成员函数的默认版本,但可以编写无限数量的基类。根据上面的分析,如果把模板和多重继承结合起来,就会产生一个非常灵活的设备,应该非常适合在库中生成“设计元素”。
五、战略和策略类
策略和策略类帮助我们设计安全、高效和高度灵活的“设计元素”。
所谓策略,就是用来定义一个类或者类模板的接口。
该界面由以下一项或所有项组成:
内隐型别定义(内部类型定义)成员函数成员变量。
让我们来定义一个可实作出创建者策略的班级。产生对象的可行办法之就是表达式新的,另一个办法是以malloc()加上放置新的操作符(迈耶斯,1998年b)。此外,还可以采用复制(克隆)方式来产生新对象。下面是三种做法的实例呈现:
做法一直接新模板类T
结构OpNewcreator
{
静态T*Create()
{
返回新t;
}
}做法2先分配内存再新的构造模板类T
结构MallocCreator
{
静态T*Create()
{
void * buf=STD:malloc(sizeof(T));
如果(!buf)返回o;
返回new(buf)T;
}
}做法3特化成员变量,构造时传入模板类T
结构原型创建者
{
公共:
PrototypeCreator(T*pObj=NULL)
:pProtoType(pObj)
{
}
T*Create()
{
返回pPrototype?pPrototype-Clone():0;
}
t * get prototype(){ return pPrototype;}
void set prototype(T * p0bj){ pPrototype=pObj;}
私人:
T * pProtoType
}
这里有一个重要观念:
政策接口和一般传统的班接口(纯虚函数集)不同,它比较松散,因为政策是语法导向(面向语法)而非标记导向(面向签名).换句话说,创建者明确定义的是"怎样的语法构造符合其所规范的类",而非"必须实现出哪些函数"。例如创建者策略并没有规范创建()必须是静电还是虚拟的,它只要求班级必须定义出创建().此外,创建者也只规定创建()应该(但非必须)传回一个指向新对象的指针。
因此创建()也许会传回0或丢出异常―—这都极有可能。
面对个政策,你可以实作出数个策略类别。它们全都必须遵守政策所定义的接口。稍后你会看到个例子,使用者选择了一个政策并应用到较大结构中。
先前定义的三个策略类,各有不同的实作方式,甚至连接口也有些不同(例如原型创造者多了两个函数GetPrototype()和setPrototype())。尽管如此,它们全都定义创建()并带有必要的返回型别,所以它们都符合创建者政策。
//库代码
模板类创建策略
widgetManager类:公共创建策略
{.}
如果班级采用一个或多个政策,我们称其为主机或主办班级。
上例的widgetManager便是"采用了一个政策"的东道主班。
主机负责把政策提供的结构和行为组成一个更复杂的结构和行为。
当客户端将WidgetManager模板具现化(实例化)时,必须传进一个他所期望的政策:
//应用程序代码
typedef小部件管理器op新创建者小部件MywidgetMgr让我们分析整个来龙去脉。无论何时,当一个MywidgetMgr对象需要产生一个小部件对象时,它便调用它的政策子对象OpNewCreator所提供的创建().选择"生成策略"(创建策略)是WidgetManager使用者的权利。藉由这样的设计,可以让WidgetManager使用者自行装配他所需要的机能。
这便是基于策略的类的设计主旨。
示例运用不同政策的方式
#包含标准视频
#包含标准库
//库代码
模板类创建策略
WidgetManager类:公共创建策略
{
};
模板类T
结构OpNewCreator
{
静态T*Create()
{
printf(OpNewcreator策略\ n’);
返回新t;
}
};
模板类T
结构MallocCreator
{
静态T*Create()
{
printf( MallocCreator Policy \ n );
void * buf=malloc(sizeof(T));
如果(!buf)返回0;
return(T *)buf;
}
};
模板类T
结构原型创建者
{
公共:
PrototypeCreator(T* pObj=NULL)
:pPrototype(pObj)
{
}
T*Create()
{
printf( PrototypeCreator \ n );
返回pPrototype?pPrototype:0;
}
t * get prototype(){ return pPrototype;}
void set prototype(T * p0bj){ pPrototype=p0bj;}
私人:
T * pPrototype
};
结构小部件{
作废打印(){
printf(这是widget \ n );
}
};
//应用程序代码
void main() {
WidgetManager OpNewCreator小部件mrg1
Widget* pWigdet=mrg1 .create();
pWigdet-Print();
Widget manager malloc creator Widget mrg 2;
pWigdet=mrg2。create();
pWigdet-Print();
WidgetManager PrototypeCreator小部件mrg3
mrg3。SetPrototype(新的小部件);
pWigdet=mrg3。create();
pWigdet-Print();
系统(“暂停”);
}1.5.1使用模板Template参数实现策略类。上面的用法首先是templatePolicy,但是在templateWidgetMrg中分成两步就有点多余和危险了。
此时,库可以使用模板模板参数来描述策略,如下所示:
//库代码
模板模板类创建的类创建策略
WidgetManager类:公共创建策略小部件
{.}Created是CreationPolicy的参数,creation policy是WidgetManager的参数。
Widget已经写入上述库中,使用时不需要再次传递参数策略。
从
WidgetManager OpNewcreator小部件mrg1简化为
WidgetManager OpNewcreator mrg1出于目的和益处,而不仅仅是为了方便,将“模板模板参数”与策略类一起使用。有时候这种用法是必不可少的,这样宿主类就可以通过模板生成不同类型的对象。例如,假设WidgetManager希望用相同的生成策略生成一个Gadget对象。代码如下:
//库代码
模板模板类创建策略
WidgetManager类:公共创建策略小部件
{
void Dosomething()
{
Gadget* pw=CreationPolicy Gadget()。create();
}
}policy确实能给widgetManager带来很大的灵活性。
首先,当您准备实例化)widgetManager时,您可以从外部更改策略,就像更改模板参数一样简单。二是可以根据节目的特殊需求提供自己的政策。你可以使用新的或者malloc或者原型或者专用于你自己系统的内存分配器。小部件就像一个小的“代码生成引擎”,你可以设置自己的代码生成方法。模板默认参数为了让应用程序开发人员的工作更轻松,widgetManager的作者应该定义一些常用的策略,并以“模板默认参数”的形式提供最常用的策略:
模板模板类类创建策略=NewOpereator
widgetManager类.与虚函数策略和虚函数非常不同。虽然虚函数也提供类似的效果:类作者用基本虚函数构建高端函数,并允许用户重写这些基本虚函数的行为。
然而,如前所述,在构建“设计元素”时,策略是必不可少的,因为它们具有丰富的类型信息和静态连接。难道不是“设计”规定了“类型在执行前如何交互,你能做什么,不能做什么”的完整规则吗?策略允许您在类型安全的前提下通过组合简单的需求来产生您的设计。
此外,因为主机类仅在编译时与其策略结合,所以它比手动程序更强大、更高效。
当然,由于策略的特性,它们不适用于动态链接和二进制接口,所以本质上,策略和传统接口并不相互竞争。
1.5.2用模板成员函数实现策略类使用“模板模板参数”的另一种情况是用模板成员函数连接所需的简单类。也就是说,策略被视为一个通用类(“通用”是相对于类模板而言的),但有一个或几个模板化的成员。
例如,我们可以将之前的Creator策略重新定义为一个非模板类,它提供了一个名为create的模板函数。因此,策略类如下所示:
结构OpNewCreator
{
模板类T
静态T*Create()
{
printf(OpNewcreator策略\ n );
返回新T;
}
};这样定义和实现的策略对旧编译器有很好的兼容性。另一方面,这种政策很难讨论、界定、实施和应用。
不及物动词更丰富的策略创建者策略只指定了一个Create()成员函数。但是,PrototypeCreator又定义了两个函数,分别是GetProtoType()和SetProtoType()。我们来分析一下。
因为WidgetManager继承了policy类,而GetPrototype()和SetPrototype()是PrototypeCreator的公共成员,所以这两个函数被添加到WidgetManager中,用户可以直接访问。
然而,WidgetManager只需要Create();这就是widgetManager所需要的,也是保证自身功能的唯一要求。但是,用户可以开发更丰富的界面。
基于原型的创建者策略类的用户可以编写以下代码:
typedef widget manager prototype creator mywidget manager;
.
Widget* perototype=.
MywidgetManager经理;
经理。set prototype(pPrototype);
.使用经理.如果之后用户决定采用不支持原型的生成策略,那么编译器会指出问题:特定于原型的接口已经被使用了。这正是我们想要的坚固设计。
这样的结果很受欢迎。如果用户需要扩展策略,他们可以在不影响host类原有功能的情况下受益于更丰富的功能。别忘了,决定使用哪种策略的是用户而不是库本身。与常见的多接口不同,策略为用户提供了类型安全的能力。
七。policy类的析构函数有一个关于建立策略类的重要细节。
在大多数情况下,宿主类将通过“公共继承”从一些策略中派生出来。
因此,用户可以自动将主机类更改为策略类,并在以后删除指针。
除非policy class定义了virtualdestructor,否则删除指向policy class的指针将会产生意外的结果,如下所示:
typedef widget manager prototype creator mywidget manager;
.
MywidgetManager wm
PrototypeCreator小部件* pCreator=wm
//可疑,但合法删除pCreator
//编译正常,但有未定义的行为
删除pCreator
简而言之,Host类会隐式转换为Policy类,导致对Policy类的析构调用,但实际实例化的是Host类,Policy类的析构会提示非法访问。
但是,如果为策略定义了一个虚拟析构函数,会阻碍策略的静态链接特性,也会影响执行效率。
很多政策没有任何数据成员,只规范行为。第一个虚函数加入后,会给对象大小带来额外的开销,所以要尽量避免虚析构函数。
解决方案一种解决方案是,当宿主类是从策略类派生的时,采用受保护的继承或私有继承。然而,这将失去政策的丰富特征(1.6节)。策略应该采用一种轻便高效的解决方案:定义一个非虚拟的受保护析构函数:
结构OpNewcreator
{
模板类T
静态T*create (){
返回新T;
}
受保护:
~OpNewCreator (){}
}因为析构函数属于受保护级别,所以只有派生类才能销毁这个策略对象。这样,外界就不可能删除一个指向策略类的指针。
因为析构函数不是虚函数,所以在大小和速度上不会有额外的开销。
八。通过不完全物化获得的选择机制。事情会变好的。C语言的一些有趣的特性创造了策略的力量。如果没有使用类模板的成员函数,编译器将不会实现它。编译器会忽略它,甚至可能不检查它的语法。
因此,主机类有机会指示和使用策略类的可选功能。例如,让我们为WidgetManager定义一个SwitchPrototype():
//库代码
模板模板类类创建策略
WidgetManager类:公共创建策略小部件
{
void switch prototype(widget * pNewPrototype)
{
CreationPolicy小部件myPolicy=* this
删除我的策略。GetPrototype()
我的政策。set prototype(pNewPrototype);
}
};如果使用“支持原型”的创建者策略来实例化widgetManager,则可以使用switchPrototype()。如果使用不支持prototype的Creator策略实例化widgetManager,并尝试使用switchPrototype(),将会出现编译错误。如果您使用“不支持原型”的创建者策略来实例化widgetManager,并且从不尝试使用switchPrototype(),则该程序是合法的。这些都意味着widgetwanager可以从灵活而丰富的接口中受益,而且只要您不尝试使用某些成员函数,它仍然可以很好地处理更简单的接口。
小部件的作者现在可以像这样定义创建者策略:
Creator设置了一个T类型的类模板,其中列出了create()。
该函数应该返回一个指向新T对象的指针。Creator实现代码可以选择性地定义两个附加函数:TGetProtoptype()和SetPrototype(T),这样用户在生成对象时就可以有机会“获取”或“设置”一个原型。在这种情况下,WidgetManager中可能会出现一个switch prototype(t * pnew prototype)函数,它可以删除当前的原型并设置一个新的原型。
当与每个策略类结合时,这些非元素实例化为您(用户)作为库设计者带来了非凡的自由。实际上,您可以创建“精简和依赖”的主机类,这些类可以使用额外的功能,并优雅地将级别降低到具有高度纪律性的最低策略。
九。组合策略当您组合策略时,它们是最有用的。
一般来说,一个高度可组装的类将使用几个策略来实现其操作的所有方面。库用户可以通过组合不同的策略类来选择他想要的高级行为。
例如,假设我们计划设计一个通用的智能指针(第7章有完整的实现)。
假设你分析了两个应该建立为策略的设计:线程模型(多线程模型)和解引用前检查(拾取前检查)。因此,您实际上创建了一个包含两个策略的类模板,名为SmartPtr:
模板
T类,
模板类类检查策略,
模板类类ThreadingModel
类SmartPtrSmartPtr有三个模板参数:
一个表示指针对象类型,另外两个是策略。在SmartPtr中,你可以使用两个策略来组织一个坚实的实际工作。SmartPtr成为了“整合几个政策”的协调层,而不是一成不变的罐头工作。以这种方式设计SmartPtr就是给用户“用简单的typedef配置smartPtr”的能力:
typedef SmartPtr小部件,无检查,单线程
widgetPtr在同一个应用程序中,您可以定义和使用几个不同的智能指针类:
typedef Smartptr小部件,EnforceNotNull,SingleThreaded
SafeWidgetPtr两种策略定义如下:
Checking:这个名为CheckingPo7icy的类模板必须公开-check()成员函数,它可以接受T*类型的左值。当智能指针将要被解引用时,将调用check(),并且将传入并检查指针对象。ThreadingModel:这个名为threading model的类模板必须提供一个名为Lock的内部类型,其构造函数接受一个T参数。对于一个锁对象,在其生命周期中对这个T对象的所有操作都是序列化的。例如,下面是两个策略类NoChecking和EnforceNotNull的实现内容:
模板类T
结构不检查
{
静态空检查(T*){ }
};
模板类T
结构EnforceNotNull
{
类NullPointerException:public STD:exception {.};
静态空隙检查(T*ptr)
{
如果(!ptr)throw NullPointerException();
}
}通过更改不同的检查策略类,您实际上可以做出各种行为。您甚至可以使用默认值来初始化指针对象)—只需传入一个指向指针的引用,如下所示:
模板类T
结构EnsureNotNull
{
静态无效检查(T* ptr){
如果(!ptr)ptr=GetDefaultvalue();
}
};SmartPtr使用如下检查策略:
模板
T类,
模板类类检查策略,
模板类类ThreadingModel
类别SmartPtr
:公共检查策略测试
,public ThreadingModel SmartPtr
{
.
t *运算符-()
{
typename threading model smart ptr:Lock guard(* this);
CheckingPolicy T : Check(指针对象_);
返回指针对象_;
}
私人:
*指针对象_;
} ;注意上面同一个函数中两个策略类CheckingPolicy和ThreadingModel的应用。根据不同的模板参数,SmartPtr : operator-会表现出两种不同的正交行为。这就是政策的合力。
一旦您尝试将一个类分解成正交的策略,您就可以使用少量的代码来涵盖大多数行为。
X.用策略类定制结构正如1.4节所提到的,模板的一个限制是你不能定制一个类的结构,只能定制它的行为。但是,基于策略的设计支持结构上的定制。
假设您想要支持“非指针形式”SmartPtr。例如,一些平台上的一些指针可能以handle的形式出现,handle是一个整数值,用于传递给系统函数以获得实际的指针。要解决这种情况,可以通过所谓的结构策略“间接”访问指针。Policy抽象了指针存储的概念,所以它应该提供一个PointerType(用于表示指针所指向的对象的类型),一个referenceType(用于表示指针所指向的对象的引用类型),以及两个函数GetPointer()和SetPointer()。
不将指针类型指定为T*是非常有益的。例如,您可以将SmartPtr应用于非标准指针类型(如段架构上的近指针和远指针),或者您可以轻松实现智能解决方案,如before和after函数(Stroustrup 2000a)。这些可能性非常有趣。
智能指针的默认存储形式是具有结构策略接口的通用指针,如下所示:
模板类T
类DefaultSmartPtrStorage
{//翻译注意:Loki没有这个类,但是有一个DefaultsPstorage
公共:
typedef T * PointerTypetypedef T ReferenceType受保护:
pointer type get pointer({ return ptr _;}
void set pointer(pointer type ptr)(ptr _=ptr;)
私人:
指针类型指针对象_;
};指针的实际存储形式已经完全隐藏在结构接口中。现在,SmartPtr可以使用Storagepolicy来替换T *(aggregating)的聚合:
模板
T类,
模板类类检查策略,
模板类类ThreadingModel,
模板类类存储=DefaultSmaltPtrStorage
类smartPtr当然,为了嵌入所需的结构,SmartPtr必须从存储继承或聚合)存储对象(详见第七章)。
XI。策略的兼容性假设你要生成两个SmartPtr: FastwidgetPtr是一个不需要检查的指针,SafewidgetPtr必须在解引用前检查。
这里有一个有趣的问题:可以将FastwidgetPtr对象赋(赋值)给safewidgetPtr对象吗?你应该能以其他方式分配它们吗?想实现这样的功能,怎么做呢?
让我们从推理开始第一步。safewidgetPtr比FastwidgetPtr有更多的限制,所以我们很容易接受“将FastwidgetPtr转换为SafewidgetPtr”的想法。这是因为C本来是支持隐式转换的,但是有一些限制,比如非const类型到const类型的转换。
另一方面,“随意将safewidgetPtr对象转换为FastwidgetPtr对象”是危险的。因为大部分应用使用safewidgetPtr,所以只有需要考虑速度的小核心代码才会考虑使用FastwidgetPtr。仅允许在明确的控制下将SafewidgetPtr转换为FastwidgetTPTR。这将有助于保持FastwidgetTPTR的最小量。
在将策略相互转换的各种方法中,最佳且最具可扩展性的实践是通过使用策略来控制SmartPtr对象的复制和初始化,如下所示(让我们将前面的程序简化为只有一个策略:Checking):
温度
T类,
临时类类检查策略
类SmartPtr:公共检查策略T
{
模板
T1类,
模板类类CP1、
SmartPtr ( const SmartPtr T1,CP1其他)
:指针对象_(其他.指针对象_),检查策略T(其他)
{ .}
} ;SmartPtr实际上制作了一个模板复制构造函数,它接受任何SmartPtr对象。其中粗线根据其参数smartPtr T1,CP1的内容初始化SmartPtr的内容。
下面是它的工作原理(请继续上面的构造函数)。假设您有一个Extendwidget类,它是从widget派生的。当你用一个智能ptr扩展小部件初始化一个智能ptr小部件时,编译器将尝试用一个扩展小部件初始化小部件(这将成功),然后用一个智能ptr小部件初始化无检查,无检查。这看起来很可疑,但是不要忘记Smartptr是从它的policy派生出来的,所以编译器很容易知道你想用Nochecking初始化Nochecking。整个初始化过程可以顺利进行。
那就有意思了。假设您打算用一个智能Ptr扩展小部件来初始化一个智能ptr小部件EnforceNotNu11,不进行检查。此时,Extendedwidget被转换成一个小部件,如前所述。然后,编译器尝试匹配smartptr扩展小部件,不使用EnforceNotNull构造函数进行检查。
如果EnforceNotNull实际上生成了一个可以接受Nochecking对象的构造函数,那么编译器将找到该构造函数并完成转换。如果NoChecking实际上做了一个可以把自己转换成EnforceNotNull的转换运算符,那么转换也可以进行。此外,还会产生编译错误。
正如你所看到的,当你进行政策转换时,双方都是有弹性的。在左边,您可以实现转换构造函数,在右边,您可以实现转换操作符。
赋值运算符也有同样的难题。幸运的是,Sutter 2000(翻译:exceptive C,第41条)描述了一种非常漂亮的技术,它允许您根据复制构造函数实际制作赋值运算符。这是一个非常漂亮的技巧。你应该看看那篇文章。Loki的Smartptr也采用了这种技术。
虽然将Nochecking转换为EnforceNotNull或反向转换是合理的,但有些转换根本不合理。想象一下,将一个引用计数的指针转换成支持其他“所有权策略”的指针,将是一个毁灭性的复制(有点像std:auto.ptr)。这种转换会导致语义错误。所谓引用计数,就是“指向同一个对象的所有指针都是大家都知道的,可以根据唯一的计数器进行跟踪”。一旦你试图设置一个指向另一个所有权策略的指针,你将破坏引用计数有效运行所依赖的不变性(恒定性)。
总之,所有权的转换不应该是含蓄的,应该特别小心地处理。你最好显式调用一个函数来改变“引用计数指针”的所有权策略。只有当源指针的引用计数值为1时,该函数才能成功转换。
十二。把班级分成一堆策略。建立基于策略的类设计最困难的部分是如何正确地将类分解成策略。一个标准是识别和命名参与类行为的设计。只要任何事情都可以用一种以上的方法解决,就应该对其进行分析,并将其从该类中删除,以成为一项政策。别忘了,埋在类设计里的约束和埋在代码里的魔法常数一样糟糕。
例如,让我们考虑widgetManager类。如果widgetManager在内部生成一个新的widget对象,那么生成方法应该推迟到策略中。如果widgetManager打算存储一大组小部件对象,一个合理的设计是将集合设计为存储策略,除非您对特定的存储机制有强烈的偏好。
在极端情况下,宿主类几乎是策略的集合,它将设计阶段的所有决策和约束委托给策略。这样的主机类只是覆盖策略集合的外层,只能处理策略组合的行为。
host类的过度泛化会造成弊端,它会有太多的模板参数。实际上,超过4~6个模板参数会导致笨拙的协作。但是,如果宿主类打算提供复杂而有用的函数,模板参数应该仍然可用。
当使用基于策略的类时,类型定义(即typedef)是一个重要的工具。这不仅是为了方便,也是为了保证有序使用和方便维护。例如,下面的类型定义:
typedef智能Ptr
小部件,
参考计数,未检查
widgetPtr在代码中使用定义冗长的Smartptr而不是上面提到的简单的widgetPtr,真的很无聊很无聊。但是,相对于程序的“可维护性”和“可读性”来说,“枯燥”还是个小问题。随着设计的发展,widgetptr的定义可能会改变,例如,它可能会更改为不同于“不检查调试”的检查策略。重要的是所有程序都采用widgetPtr,而不是“用Smartptr写的真实作品”。区别就像“函数”和“函数相等的内联函数”。虽然技术上内联函数做同样的事情,但是我们不能在它后面建立抽象。
当您将类分解成策略时,找到正交分解很重要。正交分解会产生一些完全独立的策略。很容易找到一个非正交分解的…-如果各种政策需要互相了解,那就这样吧。
比如说。想象一下智能指针中的数组策略。很简单。——指定智能指针是否指向数组。在这个策略的定义中,有-t元素at (t * ptr,unsigned int index)。
员函数,以及一个类似的const T版本。至于non-array policy,由于并没有定义ElementAt ( ),所以如果有人试图使用它,会出现编译错误。以1.6节的话来说,ElementAt ( )是一个可选用的、丰富接口下的行为。
下面是两个实作出Array policy 的 policy classes:
template class T
struct 1sArray
T ElementAt (T* ptr, unsigned int index){
return ptr [index] ;
const T ElementAt (T* ptr, unsigned int index) const{
return ptr [index〕 ;
} ;
template class T struct I sNotArray { };问题是无论smart pointer是否指向array,都会与另一个policy: destruction(析构)产生不良互动。是的,你必须使用delete来摧毁指针所指对象,却必须使用delete[]来摧毁指针所指的obiect array。
两个policies之间如果没有互相影响,才称为正交(orthogonal)。根据这个定义,Array和 Destroy policies不是正交。
如果你仍然需要将array的生成和摧毁设为独立的policy,你得建立一个让它们沟通的办法。你必须让Array policy除了提供一个函数外,还提供一个bool常数,并将它传入Destroy policy。这会使Array和 Destroy的设计变得更复杂,而且不由得多了些约束条件( constraints)。非正交的 policies是不完美的设计,应该尽量避免,因为这样的设计会降低编译期型别安全性( type safety),并导致host class和 policy class的设计更加复杂。
如果你必须使用非正交的policies,请尽可能借着“把policy class当做引数传给其他policy classtemplate function”来降低相依性。这样一来你还是可以从template-based接口带来的弹性中获得利益。剩下的缺点就是,policy必须暴露它的某些实作细节给其他policy,这会降低封装性。
摘要“设计”就是一种“选择”。大多数时候我们的困难并不在于找不到解决方案,而是有太多解决方案。你必须知道哪一组方案可以圆满解决问题。大至架构层面,小至代码片段,都需要抉择。此外,抉择是可以组合的,这给设计带来了可怕的多样性。
为了在合理大小的代码中因应设计的多样性,我们应该发展出一个以设计为导向( designoriented)的程序库,并在其中运用一些特别技术。这些被特意构想出来用以支持巨大弹性的代码产生器,由小量基本设备(primitive device)组合而成。程序库本身供应有一定数量的基本设备。此外,程序库也供应一些用以建立基本设备的规格,因此客端(client)可以建造出自己想要的设备。这基本上使得 policy-based design 成为开放式架构。这些基本设备我们称为policies,其实作品则被称为policy classes。
Policies机制由 templates和多重继承组成。一个class 如果使用了policies,我们称其为host class,那是一个拥有多个template参数(通常是“template template参数”)的class template,每–个参数代表一个 policy。Host class的所有机能都来自 policies,运作起来就像是一个聚合了数个policies的容器。
环绕着policies 而设计出来的classes,支持“可扩充的行为”和“优雅的机能削减”。由于采用“public继承”之故,policy得以通过host class提供追加机能。而 host classes也能运用“policy提供的选择性机能”实作出更丰富的功能。如果某个选择性机能不存在,host class还是可以成功编译,前提是该选择性机能未被真正用上。
Policies 的最大威力来自于它们可以互相混合搭配。一个 policy-based class可以组合“policies实作出来的某些简单行为”而提供非常多的行为。这极有效地使 policies成为对付“设计期多样性”的好武器。
通过 policy classes,你不但可以定制行为,也可以定制结构。这个重要的性质使得policy-baseddesign超越了简单的型别泛化(type genericity)——后者对于容器类( container classes)效力卓著。
型别转换对policy-based classes 而言也是一种弹性的表现。如果你采用 policy-by-policy拷贝方式,每个policy 都能藉由提供适当的转型构选函数或转型操作符(甚至两者都提供)来控制它自己接受哪个policies,或它自己可以转换为哪个policy。
欲将class分解为policies时,你应该遵守两条重要准则。第一,把你的class 内的“设计决定”局部化、命名、分离出来。这也许是一种取舍,也许需要以其他方式明智地完成。第二,找出正交的policies———也就是彼此之间无交互作用、可独立更动的 policies。
总结
©
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。