modern c++ design01

 

基于PolicyClass设计

 

 

软件设计的核心理念是设计原则,设计原则讲的是为了使软件易维护,易扩展同时保持健壮性的一些“公理”。之所以说是公理是因为它是软件行业诞生以来的若干人经验的积累,经过检验符合客观实际,我猜也许最根本的是符合人们的思维习惯和认知规律。

 

同许多公理一样,软件设计的公理(设计原则)也是内涵简洁,外延无限大的东西。乍看之下很容易盲从,如果没有那么多的设计模式来支撑它,我们对它的认识也许永远只停留在内涵的认知上。设计原则与设计模式的关系就如同是世界观与方法论,前者是总纲,后者是具体论述。用另外一种哲学上的说法前者是形而上的“道”,后者是形而下的“器”。

 

语法有效,语义无效指的是正确的实现了想要的功能,但模块功能划分并不那么直观,存在一些一眼看上去不能领会其具体含义的类,这些类的设置通常是出于语言本身的局限性,如果C++能像matlab那样“算草纸”式的编程,就不需要这么多的设计了。设计就是填平人脑中的认知模型和“愚蠢”的语言这个鸿沟的。

 

 

背景

 

程序库不可能为使用者实现所有的功能,即使把所有基本功能都以base class的形式提供出来让使用者去继承派生也是不现实的,因为需要实现的base class实在太多了,即使这样的库实现出来也有很大的学习和维护负担。而且能否实现这样的base class 也还是个未知,base class的数据从哪里来?如果只针对某几种类型提供实现,那就谈不上通用性,因此数据能否也抽象一下?而模板就是专为数据抽象而做的,从最基本的int, short , char 到自定义类型都可以简单的用一个T来表示。如同函数操作的是int, char ,double,模板操作的是类型,如果把某个类型写死了,就如同函数里写进了一个魔鬼数。

 

 

 

最初看到模板的表示可能会有些犹豫:

F:如果我定义的template class 里面对一个类型调用它没有的操作怎么办?

Q:如果这样做将不能通过编译。可是你为什么要调用类型没有的操作呢?

F:是你说T代表类型,那我就可以把“任意”的类型放进去了,反正你又没具体说什么类型不可以放进去。

Q:不是所有类型都可以的,只有具有你调用的操作的类型才可以。

F:那T就不通用了啊

Q:通用仅仅是对有同样操作的类型才可以,如果非要把某个没有相应操作的类型放进去,请先为这个类型提供一份操作的实现,可以是member function或者是friend function。比如你有一个CDuck,虽然它没有Display这样的操作,你完全可以把它实现出来,甚者是个空操作也可以。这样就就可以把它放进去了。

F:明白了,原来通用也只是一定范围内的通用,不是全宇宙通用。

Q:没错,如果换个角度思考这个问题可能就轻松些。类型其实也可以看作是“对象”,比如在windows里面内核对象类型,如event, mutex等,在内核里也是作为对象来保存的。所以类型(event, mutex)和你根据这些类型定义出来的具体对象(CreateEvent后得到的那个Event 实例,虽然你只有这个实例的“钥匙”——句柄,但它真的存在)都会放在内核里的一个对象目录中,所有带“名字”的对象都在里面了,包括对象类型。可见类型就是对象,但对象不一定是类型。在Python里面也是把类型作为了一种对象。如果脱离语言层面而把这个问题泛化,比如说人,我们把人分为两种类型,一类是好人,一类是坏人,单就具体一个人来说,如果他什么也没做我们不能说它是好人还是坏人,只有通过他做过的事情才可以粗略的说他是好人,或者是坏人。好人坏人这些概念的内涵实际是一组“操作”,具备这类操作的人就被划分到他对应的类别里。类别的本质是操作,而不是它里面的数据。

 

 

什么是policy?和虚函数有什么区别?

 

DIY一台电脑,每个部件都有n种选择,视资金,用途而定。假如一套配置算下来超出了预算还可以把某些不关键的部件稍稍降低一点,虽然这些部件之间也有关联,譬如选了I家的CPU,主板就会被限定在一个范围之内,但还是不唯一,仍有许多选择。Policy就像是这些部件,可以视软件整体用途,性能而定,如果安全是最重要的,那就请选带IndexCheckingPolicy,如果性能是第一位的,那就选NoChecking的。就如同钱多就选四核cpu,钱少就选双核,而选哪个都可以实现DIY一台电脑的目的。

 

再看看如果是虚函数会是什么情况。

市面上只有I家的CPU,其他部件选择不考虑。假设I家的CPU不是很符合你的需要,你想让它针对某一类运算能做些特殊处理,这基本上不可能。再假设你可以自己改装CPU,那就要自己去实现里面的电路。类比到软件上就是库里面提供了一个虚函数,并且有默认的实现,我们派生了这个类,然而想要做些特殊处理,那只能是再重新实现一下。弹性显然不如可以更换一个满足需求的CPU来得容易。

 

 

 

语法细节

 

Policy可以使模板,也可以是常规的class。模板类如果特化就是一个常规的class

 

 

template template 参数实现的Policy有个好处,就是你在成员函数实现的时候还可以用别的类型。

 

 

 

Policyhost的基类,所以就存在把host转为Policy的可能。如果delete一个Policy类型的host对象就要求Policy必须是虚函数,否则可能只delete掉一部分,轻则有内存泄露,重则崩溃。有两种解决办法,一是给policy增加虚函数,这必然要引入一个vptr,或者是把policy的析构函数声明为protected,这样就不能用policy类型的指针delete一个host类型的对象了。具体视设计需要采用哪种方法。

 


 

 

不完全具现化获得的选择性能。利用编译器的一个“漏洞”,如果class Template有一个成员函数未曾被用到,它就不会被编译器编译出来,连语法检查都不做。就是说只是声明一下没关系,只要不使用就ok

 

 

CreationPolicy可能并有很多选择,不是每种都有GetPrototypeSetPrototype的,假设提供了一种没有GetPrototypeSetPrototypepolicy,只要你不调用SwitchPrototype就没有关系,编译器不会理会它,甚至连语法检查都不做,只有template class才有这个待遇。

 

有了Policy,下一步就是把他们组合起来实现想要的功能

 

 

 

虽然class T, template <class> class CheckingPolicytemplate <class> class ThreadingModel地位相同,都是SmartPtrtemplate 参数,但是后两个template template参数却可以把T作为template参数,招式有了,刀也要锋利才行,这些细节就是让刀锋利的磨刀石。

 

 

Policy之间的转换是通过以实现policy来控制host的对象拷贝和初始化。

 

例如:

 

 

 

 

 

 

SmartPtr的拷贝构造函数需要分别调用每个Policy的拷贝构造函数,在上面的例子中以T1来初始化通常可以,我们暂且认为它是一个指针。而以SmartPtr<T1, CP1>&来初始化CheckingPolicy<T>就要仔细研究一下了。SmartPtr<T1, CP1>CP1的派生类,它的类型是CP1<T1>,是否能与CheckingPolicy<T>进行转换呢?

 

 

 

STL中我们可以看到很多这样的写法:

 

这种template拷贝构造函数就是用来解决同一个模板,不同模板参数之间的转换的。 如果SmartPolicy中的CheckingPolicy<T>具体类型有一个这样的拷贝构造函数,那么上面的转型就可以成功,否则就不确定。

 

 

从数学的角度看Policy

将一个Class分解为一堆Policies.类比数学里的正交分解,假如有向量V1V2,它们之间的夹角为a, V1 – V1*cos(a)就可以去除V1中V2方向的分量,得到一个垂直于V2的向量,这时两个向量既为正交。正交本质含义就是功能无重合,对应到软件里两个类之间的功能如果有重合也应该考虑把另外一个类的功能抽离出来,在功能上不与其他的类有任何相似的地方。虽然无法像数学上那样用符号精确描述,但却可以作为一种指导思想,如果程序里嗅到了两个类功能上有部分重合,那就请果断的拿出来。就好比你不期望CPU还可以完成部分内存的功能,也不期望内存还有计算的能力,把自己的事情做好比什么都重要。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值