泛型基础特性分析

一、

class template

成员函式᳾曾被用到,它不会被编译器具体实现出来。编译器不理会它,甚至也许不会为它进
行语法检验
5
如此
来造成 host class 有机会指明并使用 policy class 的可选特性。举个例子,让我们为
WidgetManager 定义 SwitchPrototype()
// Library code
template <template <class> class CreationPolicy>
class WidgetManager : public CreationPolicy<Widget>
{
...
void SwitchPrototype(Widget* pNewPrototype)
{
CreationPolicy<Widget>& myPolicy = *this;
delete myPolicy.GetPrototype();
myPolicy.SetPrototype(pNewPrototype);
}

};

! 如果你采用个「支持prototype」的 Creator policy 来具现化WidgetManager,你便可以
使用 SwitchPrototype()
! 如果你采用个「不支持prototype」的 Creator policy 来具现化WidgetManager,并尝试
使用 SwitchPrototype(),会出现编译错误。
! 如果你采用个「不支持prototype」的 Creator policy 来具现化WidgetManager,并且从
᳾试图使用 SwitchPrototype(),程序是合法的。
这些都意味 WidgetManager除了可从弹性且丰富的接口得到好处之外,也仍然可以搭配较简
单的接口而正常运作 ― 只要你不试着去使用其某些成员函式。

二、
当你将 policies 组合起来,便是它们最有用的时候。 般而言, 个高度可组装化的 class 会运
用数个
policies 来达成其运作 的多种面向。 个链接库使用者可以藉由组合不同的 policy
classes
来选择他所需的高阶行为。
举个例子,假设我们正打算设计
个泛型的 smart pointer 。 假设你分析
出两个得被建立为
policies 的设计: threading model (多绪模型)和 check before dereference
(提领前先检验)。 于是你实作出 个带有两个 policies class template ,名为 SmartPtr
template <
class T,
template <class> class CheckingPolicy,
template <class> class ThreadingModel
>
class SmartPtr;
SmartPtr template 参数: 个代表 pointee type (被指物件的型别) ,另两个是 policies
SmartPtr 你可以运用两个 policies 组织出 份稳固的实作品。 SmartPtr 成为「整合数
policies 」的协调层,而非 成不变的罐装实作品。以这种方式来设计 SmartPtr ,便是赋予
使用者「以简单的 typedef SmartPtr 进行组态( configure )」 的能力:
typedef SmartPtr<Widget, NoChecking, SingleThreaded>
WidgetPtr;

在同 个应用程序 ,你可以定义并使用数种不同的 smart pointer classes
typedef SmartPtr<Widget, EnforceNotNull, SingleThreaded>
SafeWidgetPtr;
两个 policies 定义如
Checking :这个名为 CheckingPolicy<T> class template 必须公开 Check() 成员函式,
可接受 个型别为 T* 的左值。当 smart pointer 即将被提领( dereference )时会呼叫 Check()
传入被指对象( pointee object )并作检查。
ThreadingModel :这个名为 ThreadingModel<T> class template 必须提供 个名为 Lock
内部型别,该型别的建构式接受
T& 参数。对 Lock 对象来说,其生命 所有对此 T
对象的操作行为都是循序的、次第的( serialized )。
举个例子,
面是两个 policy classes NoChecking EnforceNotNull 的实作内容:
template <class T> struct NoChecking
{
static void Check(T*) {}
};
template <class T> struct EnforceNotNull
{
class NullPointerException : public std::exception { ... };
static void Check(T* ptr)
{
if (!ptr) throw NullPointerException();
}
};
藉由抽换不同的 Checking policy class ,你可以实作出各种不同行为。你甚至可以利用默认值来
初始化被指对象(
pointee object 只要传入 reference-to-pointer 即可,像这样:
template <class T> struct EnsureNotNull
{
static void Check(T*& ptr)
{
if (!ptr) ptr = GetDefaultValue();
}
};
SmartPtr 这样使用 Checking policy
template
<
class T,
template <class> class CheckingPolicy,
template <class> class ThreadingModel
>
class SmartPtr
: public CheckingPolicy<T>
, public ThreadingModel<SmartPtr>

{
...
T* operator->()
{
typename ThreadingModel<SmartPtr>::Lock guard(*this);
CheckingPolicy<T>::Check(pointee_);
return pointee_;
}
private:
T* pointee_;
};
注意 述同 个函式 CheckingPolicy ThreadingModel 两个 policy classes 的运用。根
据不同的
template 自变量, SmartPtr::operator-> 会表现出两种不同的正交( orthogonal )行
为。这正是
policies 的组合威力所在。
旦你设法把 class 分解成正交的 policies ,便可利用少量程序代码涵盖大多数行为 
三、
假设你想支持「非指标形式」的 SmartPtr ,例如某些平台 的某些指针也许会以 handle 型式
呈现,这是
种用来传给系统函式的整数值,藉以取得实际指标。为了解决这种情况,你可以
透过
个所谓的 Structure policy 将指标的存取「间接化」。 Structure policy 将指标的储存概念
抽象化,因此它应该提供
PointerType 型别(用以代表指针所指对象的型别)、
ReferenceType 型别(用以代表指针所指对象的 reference 型别), 以及 GetPointer()
SetPointer() 两函式。
不把 pointer 型别硬性规定为 T* ,这种作法带来重大好处。例如你可以将 SmartPtr 应用于非
标准型别之
(有点像是 segment 架构 near 指标和 far 指标), 或者你可以轻松实作出
灵巧解法诸如
before after 函式( Stroustrup 2000a )。 这些可能性都非常有趣。
smart pointer 的预设储存型式是 个带有 Structure policy 接口的 般指针,像 面这样:
template <class T>
class DefaultSmartPtrStorage //
译注 Loki : 无此 class 但有 , 个 DefaultSPStorage
{
public:
typedef T* PointerType;
typedef T& ReferenceType;
protected:
PointerType GetPointer() { return ptr_; }
void SetPointer(PointerType ptr) { ptr_ = ptr; }

private:
PointerType pointee_;
};
实际指针的储存型式已被完全隐蔽于 Structure 接口之内 现在 。 SmartPtr , 可以运用 Storage
policy 来取代对 T* 的聚合( aggregating ):
template
<
class T,
template <class> class CheckingPolicy,
template <class> class ThreadingModel,
template <class> class Storage = DefaultSmartPtrStorage
>
class SmartPtr;
当然,为了内嵌所需的结构, SmartPtr 必须继承自 Storage<T> 或聚合( aggregate
Storage<T> 物件。
四、
假设你要产生两个 SmartPtr FastWidgetPtr 是个不需检验的指标, SafeWidgetPtr 则必须
在提领(
dereference )之前先检验。这时有个有趣的问题:你能将 FastWidgetPtr 物件指
派(赋值)给
SafeWidgetPtr 物件吗?你应该有能力以其它方法指派它们吗?如果你想
实现出这样的功能,该如何实作?
让我们从推理跨出第
步。 SafeWidgetPtr FastWidgetPtr 有更多限制,因此我们很容易
接受「把
FastWidgetPtr 转为 SafeWidgetPtr 」的想法。这是因为 C++ 原就支持隐式转换
implicit conversion ), 不过也存在 些限制,例如 non-const 型别转为 const 型别。
从另
方面说,「 自由 SafeWidgetPtr 对象转换为 FastWidgetPtr 对象」是危险的。因
为应用程序大都使用
SafeWidgetPtr ,只有小型且需要考虑速度的核心程序代码才会考虑使用
FastWidgetPtr 。只在明确受控的情况 才允许将 SafeWidgetPtr 转换为 FastWidgetPtr
此举将有助于保持 FastWidgetPtr 的最小用量。
Policies 之间彼此转换的各种方法 ,最好又最具扩充性的实作法是以 policy 来控制 SmartPtr
对象的拷贝和初始化,如 所示(让我们将先前程序简化为只有 policy Checking )。
template
<
class T,
template <class> class CheckingPolicy
>
class SmartPtr : public CheckingPolicy<T>
{
...
template

<
class T1,
template <class> class CP1,
>
SmartPtr(const SmartPtr<T1, CP1>& other)
: pointee_(other.pointee_), CheckingPolicy<T>(other)
{ ... }
};
SmartPtr 实作出 个「接受任何 SmartPtr 对象」的 template copy 建构式。其 粗体那
行系根据其自变量 SmartPtr<T1,CP1> 的内容,将 SmartPtr 的内容初始化。
面介绍其运作方式 (请接续 述建构式) 。假设你有个 ExtendWidget class ,衍生自 Widget
当 你 以 SmartPtr<ExtendedWidget,NoChecking> 初 始 化 SmartPtr<Widget,
NoChecking>
时,编译器会试着以 ExtendWidget* 初始化 Widget* (这会成功), 然后
SmartPtr<Widget,NoChecking> 初始化 NoChecking 。这看起来很可疑,但是别忘了
SmartPtr 衍生自其 policy ,所以编译器可以轻易知道你想要以 NoChecking 初始化
NoChecking 。整个初始化过程可以良好进行。
来就有趣了。假设你打算以 SmartPtr<ExtendedWidget,NoChecking> 初始化
SmartPtr<Widget,EnforceNotNull> 。此时 ExtendedWidget* 被转为 Widget* 如前面
所 说 。 然 后 编 译 器 试 图 将 SmartPtr<ExtendedWidget,NoChecking> 拿 来 匹 配
EnforceNotNull 建构式。
如果
EnforceNotNull 实作出可接受 NoChecking 对象的建构式,那么编译器会找到那个建构
式,完成转换。如果
NoChecking 实作出可将自己转换为 EnforceNotNull 的转型运算子,那
么转换也可以进行。除此之外,都会产生编译错误。
如你所见,当你进行
policies 转换时,两边都有弹性。左手边你可以实作转型建构式,右手边
你可以实作转型运算子。
assigment 运算子也有 样的难缠问题,幸运的是 Sutter 2000 (译注: Exceptional C++, 条款 41
阐述了
种非常漂亮的技术,可以让你根据 copy 建构式实作出 assignment 运算子。这是
非常漂亮的手法,你应该读读那篇文章。
Loki SmartPtr 也运用了这项技术。
虽然
NoChecking 转换为 EnforceNotNull 或反向转换感觉都十分合理,但有些转换却是
也不合理。想象将
reference-counted 指标转换为 个支持其它「 ownership (拥有权)策略」
的指标,将是
场毁灭性的 copy (有点像 std::auto_ptr )。 这样的转换造成语意 的错误。
所谓 reference counting 是「所有指向同 对象的指针都为大家所知,并且可根据 个独
的计数器加以追踪」, 旦你尝试将某个指标设为另 ownership policy ,你便是破坏了
reference counting 赖以有效运作的不变性(恒长性)。
总之,
ownership 的转换不该是隐式转换,应该特别小心处理。你最好明确呼叫某个函式来改
变「
reference-counted 指标」的 ownership policy 。唯有源端指标的 reference count 数值为 1 ,这
个函式才有可能转换成


总结:
「设计」就是 种「选择」。 大多数时候我们的困难并不在于找不到解决方案,而是有太多解
决方案。你必须知道哪
组方案可以圆满解决问题。大至架构层面,小至程序代码片段,都需要
抉择。此外,抉择是可以组合的,这给设计带来了可怕的多样性。
为了在合理大小的程序代码
因应设计的多样性,我们应该发展出 个以设计为导向( design
oriented )的链接库,并在其运用 些特别技术。这些被特意构想出来用以支持巨大弹性的程
式码产生器,由小量基ᴀ装置( 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-based
design
超越了简单的型别泛化( type genericity 后者对于容器类别( container classes )效力
卓著。
型别转换对
policy-based classes 而言也是 种弹性的表现。如果你采用 policy-by-policy 拷贝方
式,每个
policy 都能藉由提供适当的转型建构式或转型运算子(甚至两者都提供)来控制它自
己接受哪个
policies ,或它自己可以转换为哪个 policy
欲将
class 分解为 policies 时,你应该遵守两条重要准则。第 ,把你的 class 内的「设计决定」
局部化、命名、分离出来。这也许是
种取舍,也许需要以其它方式明智 完成。第 ,找出
正交的
policies — 也就是彼此之间无交互作用、可独立更动的 policies


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值