条款25:将constructor和non-member functions虚化(1)
第一次面对"virtual constructors"时,似乎不觉得有什么道理可言。是的,当你手上有一个对象的 pointer或 reference,而你不知道该对象的真正类型是什么的时候,你会调用virtual function(虚函数)以完成"因类型而异的行为"。当你尚未获得对象,但已经确知需要什么类型的时候,你会调用 constructor 以构造对象。那么,谁能够告诉我什么是 virtual constructors 呢?
很简单。虽然 virtual constructors 似乎有点荒谬,但它们很有用(如果你认为荒谬的想法都是没有用的,你如何解释现代物理学的成功?)。假设你写了一个软件,用来处理时事新闻,其内容由文字和图形构成。你可以把程序组织成这样:
- class NLComponent { // 抽象基类,用于时事消息
- public: // 的组件(components),
- ... // 其中内含至少一个纯虚函数。
- };
-
- class TextBlock: public NLComponent {
- public:
- ... // 没有内含任何纯虚函数。
- };
-
- class Graphic: public NLComponent {
- public:
- ... // 没有内含任何纯虚函数。
- };
-
- class NewsLetter { // 一份时事通信是由一系列的
- public: // NLComponent 对象构成的。
- ...
- private:
- list<NLComponent*> components;
- };
这些 classes 彼此间的关系如下:
NewsLetter 所使用的 list class由 Standard Template Library 提供,后者是 C++(www.cppentry.com) 标准程序库的一部分(见条款E49和条款35)。list 对象的行为就像双向链表(doubly linked lists)--尽管它们不一定得用双向链表实现。
NewsLetter 对象尚未开始运作的时候,可能存储于磁盘中。为了能够根据磁盘上的数据产出一份 Newsletter,如果我们让NewsLetter 拥有一个 constructor 并用 istream 作为自变量,会很方便。这个 constructor 将从 stream 读取数据以便产生必要的核心数据结构:
- class NewsLetter {
- public:
- NewsLetter(istream& str);
- ...
- };
-
- 此 constructor 的伪代码(pseudo code)可能看起来像这样:
-
- NewsLetter::NewsLetter(istream& str)
- {
- while (str) {
- read the next component object from str;
-
- add the object to the list of this
- newsletter's components;
- }
- }
或者,如果将棘手的东西搬移到另一个名为readComponent的函数,就变成这样:
- class NewsLetter {
- public:
- ...
- private:
- // 从 str 读取下一个 NLComponent 的数据,
- // 产生组件(component),并返回一个指针指向它。
- static NLComponent * readComponent(istream& str);
- ...
- };
-
- NewsLetter::NewsLetter(istream& str)
- {
- while (str) {
- // 将 readComponent 返回的指针加到 components list 尾端,
- // "push_back"是一个 list member function,用来将对象安插
- // 到 list 尾端。
- components.push_back(readComponent(str));
- }
- }
思考一下,readComponent 做了些什么事。它产生一个崭新对象,或许是个 TextBlock,或许是个Graphic,视读入的数据而定。由于它产生新对象,所以行为仿若constructor,但它能够产生不同类型的对象,所以我们称它为一个virtual constructor。所谓 virtual constructor 是某种函数,视其获得的输入,可产生不同类型的对象。Virtual constructors 在许多情况下有用,其中之一就是从磁盘(或网络或磁带等)读取对象信息。
有一种特别的 virtual constructor--所谓 virtual copy constructor--也被广泛地运用。Virtual copy constructor 会返回一个指针,指向其调用者(某对象)的一个新副本。基于这种行为,virtual copy constructors 通常以 copySelf 或 cloneSelf 命名,或者像下面一样命名为 clone。很少有其他函数能够比这个函数有更直接易懂的实现方式了:
- class NLComponent {
- public:
- // 声明 virtual copy constructor
- virtual NLComponent * clone() const = 0;
- ...
- };
-
- class TextBlock: public NLComponent {
- public:
- virtual TextBlock * clone() const // virtual copy constructor
- { return new TextBlock(*this); }
- ...
- };
-
- class Graphic: public NLComponent {
- public:
- virtual Graphic * clone() const // virtual copy constructor
- { return new Graphic(*this); }
- ...
- };