模板的乐趣:第 1 部分






4.39/5 (22投票s)
2003年6月12日
5分钟阅读

78597
探讨 C++ 模板的一些鲜为人知的特性。
引言
与 C++ 的许多特性一样,模板类主要用于它们被设计出来的直接任务,使程序员能够创建可重用的类,这些类可以抽象出它们要操作的类型。一个鲜为人知的事实是,模板几乎可以应用于类定义中出现的任何令牌,这打开了一些有趣的可能。
这也是我第一次涉足 Code Project 文章写作的领域,所以任何反馈都将非常感谢。本文没有提供示例代码,原因是本文的重点是设计原则,而不是特定的代码。
使用模板指定父类
你知道可以使用模板来指定你的模板类继承自哪个类吗?
考虑以下代码片段
template <class Parent> class MyClass : public Parent { public: void Foo(); };
简单。通过将 Parent
指定为模板参数,您现在可以通过类声明中的父类来进行实例化,如下所示:
MyClass<CWnd> m_MyWndDerivedClass
m_MyWndDerivedClass
现在是 MyClass
的一个实例,而 MyClass
又继承了 CWnd
的行为。通过更改模板参数,您可以动态更改 MyClass
继承自哪个类。这意味着 MyClass
中定义的任何行为都可以应用于它继承自的任何类。有点像反向的多态,尽管不如它强大。当然,重要的是要确保您的类具有不会与任何可预见的父类发生冲突的成员函数和变量,因此最好为所有变量/函数名添加一个附加标识符——例如 MyClass_Init()
。
这有什么用呢?可能性是无限的。但有一个特别好的原因可以说明这项功能非常有用,那就是当你开始探索策略类时。
策略类系统
策略类系统建立在这样一个原则上:与其定义具有预定行为的具体类,不如实际设计名为“策略”的小型“与任务相关的”类。当执行任务有多种方法时,可以使用策略类来反映行为。然后,最终用户可以通过任何策略组合来构建一个复合类。例如,一个复合类可以由三个主要策略组成:“创建策略”,它处理对象的创建方式、创建位置、内存分配方式等等;“初始化策略”,它可以指定类的准备使用方式、所有值是否设置为 0、类是否应从文件中读取数据等等;最后一个策略可以是“功能策略”,它可以指定类是否应将记录添加到数据库、向某人发送电子邮件,等等。说实话,基于策略的类永远不会有如此笼统的策略,比如“它实际做什么”,但它作为一个例子是很好的。
那么,如何创建策略呢?
首先,你需要决定哪些策略与类相关。在上面的例子中,我描述了三个策略:“创建”、“初始化”和“功能”。策略声明为一个标准的模板类,如下所示:
template <class T> class CreationNew { public: static T* Create() { return new T; } };
通过编写这个类,我们强制执行了所有创建策略之间必须共享的通用接口,即它们都必须包含一个不带参数的 Create
方法,并返回一个指向 T
对象的指针。如果您想进一步强制执行此接口,可以继承自一个抽象接口。
这是我们系统可能使用的创建策略之一。CreationNew
是最基本的创建方法,它只使用 new
操作符分配内存,并返回一个指针。这是另一个:
template <class T> class CreationPrototype { public: static T* Create() { return new T(_item); }; protected: static T *_item; };
此创建策略使用一个原型,在请求类型为 T
的新对象时会克隆该原型。
创建初始化策略的方法相同,但它们的接口会有所不同,因为它们都包含一个名为 Init
的成员函数,如下所示:
template <class T> class InitialisationZero { public: static void Init(T *pObj) { memset(pObj, 0, sizeof(T)); }; };
最后是功能策略,但正如我之前所说,为类的功能设置如此笼统的策略并不是一个好主意。毕竟,您设计该类是有特定原因的。策略类应该关注完成同一任务的不同方法,而不是完全不同的任务。
class FunctionalityHelloWorld { public: void DoSomething() { cout << "Hello World!"; }; };
同样,这里的通用接口是 DoSomething
函数,它应该存在于所有“功能策略”中。
那么,既然我们已经设计了所有的策略,如何创建利用它们的复合类呢?
template < class CreationPolicy, class InitPolicy, class FunctionalityPolicy > class CompoundClass : public CreationPolicy, public InitPolicy, public FunctionalityPolicy { };
这样就很简单了,现在最终用户可以通过 typedef
s 来构建各种策略的复合类,如下所示:
typedef CompoundClass< CreationNew<MyClass>, InitialisationZero<MyClass>, FunctionalityHelloWorld > MyCompoundClass; typedef CompoundClass< CreationPrototype<MyClass>, InitialisationZero<MyClass>, FunctionalitySendMail > MyOtherCompoundClass;
MyCompoundClass
现在将有三个函数:Create
、Init
和 DoSomething
,每个函数都根据用户想要它们如何工作进行了精确的定制。通过在模板参数中指定 MyClass
,我们还可以指定复合类与哪个类一起工作。这对于创建对象工厂等非常有用,尤其是在使用类型列表时(以后会更多地介绍)。
但是,假设您已经知道 CompoundClass
要与哪个类一起工作?在这种情况下,不得不显式地在模板参数中指定该类只是给最终用户增加了更多麻烦。这时就轮到了“模板模板参数”。不,这不是拼写错误。
考虑以下内容
template < template < class Created > class CreationPolicy > class CompoundClass : public CreationPolicy<MyClass> { };
天哪,嵌套模板!
确实,通过包含这个额外的模板声明,我们允许复合类本身指定模板参数,从而使我们可以这样声明 typedef
s:
typedef CompoundClass< CreationNew, InitialisationZero, FunctionalityHelloWorld > MyCompoundClass; typedef CompoundClass< CreationPrototype, InitialisationZero, FunctionalitySendMail > MyOtherCompoundClass;
整洁多了。
希望这将有助于了解策略类的强大之处,因为它们提供了极大的灵活性,并且不像某些更标准的面向对象范例那样限制其使用。
我的第一篇文章到此结束。很短,是的,但希望它能让一些人了解模板的一些更晦涩但更有用的用法。
在我的下一篇文章中,我将探索更多关于模板的怪异之处,因为我就是这样的人。
深入阅读
- Modern C++ Design(作者:Andrei Alexandrescu)讨论了策略类系统以及许多其他的“我不知道 C++ 还可以这样!”的宝石,我强烈推荐它。