C++构造函数虚拟化
虚拟构造函数
当你有一个指针或引用,但是不知道其指向对象的真实类型是什么时,你可以调用虚拟函数来完成特定类型(type-specific)对象的行为。仅当你还没拥有一个对象但是你确切地知道想要对象的类型时,你才会调用构造函数。那么虚拟构造函数又从何谈起呢?
例如假设你编写一个程序,用来进行新闻报道的工作,一条新闻报道由文字或图片组成。你可以这样管理它们:
class NLComponent { //用于 newsletter components 的抽象基类 public: ... //包含至少一个纯虚函数 }; class TextBlock: public NLComponent { public: ... // 不包含纯虚函数 }; class Graphic: public NLComponent { public: ... // 不包含纯虚函数 }; class NewsLetter { // 一个 newsletter 对象由NLComponent 对象的链表组成 public: NewsLetter(istream& str); ... private: list<NLComponent*> components; };
在NewsLetter中使用的list类是一个标准模板类(STL)。对象NewLetter不运行时就会存储在磁盘上。为了能够通过位于磁盘的替代物来建立Newsletter对象,让NewLetter的构造函数带有istream参数是一种很方便的方法。当构造函数需要一些核心的数据结构时,它就从流中读取信息。此构造函数的伪代码是这样的:
NewsLetter::NewsLetter(istream& str) { while (str) { 从str读取下一个component对象; 把对象加入到newsletter的 components 对象的链表中去; } }
或者,把这种技巧用于另一个独立出来的函数叫做readComponent,如下所示:
class NewsLetter { public: ... private: // 为建立下一个NLComponent对象从str读取数据, // 建立component 并返回一个指针。 static NLComponent * readComponent(istream& str); ... }; NewsLetter::NewsLetter(istream& str) { while (str) { // 把readComponent返回的指针添加到components链表的最后, // "push_back" 一个链表的成员函数,用来在链表最后进行插入操作。 components.push_back(readComponent(str)); } }
考虑一下readComponent所做的工作。它根据所读取的数据建立了一个新对象,或是TextBlock或是Graphic。因为它能建立新对象,它的行为与构造函数相似,而且因为它能建立不同类型的对象,我们称它为虚拟构造函数。虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象。
虚拟拷贝构造函数
还有一种特殊种类的虚拟构造函数――虚拟拷贝构造函数――也有着广泛的用途。虚拟拷贝构造函数能返回一个指针,指向调用该函数的对象的新拷贝。因为这种行为特性,虚拟拷贝构造函数的名字一般都是copySelf,cloneSelf或者是象下面这样就叫做clone。很少会有函数能以这么直接的方式实现它:
class NLComponent { public: // declaration of 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); } ... };
类的虚拟拷贝构造函数只是调用它们真正的拷贝构造函数。因此”拷贝”的含义与真正的拷贝构造函数相同。如果真正的拷贝构造函数只做了简单的拷贝,那么虚拟拷贝构造函数也做简单的拷贝。如果真正的拷贝构造函数做了全面的拷贝,那么虚拟拷贝构造函数也做全面的拷贝。
注意上述代码的实现利用了最近才被采纳的较宽松的虚拟函数返回值类型规则。被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型。如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)。这不是C++的类型检查上的漏洞,它使得又可能声明象虚拟构造函数这样的函数。这就是为什么TextBlock的clone函数能够返回TextBlock*和Graphic的clone能够返回Graphic*的原因,即使NLCompo-nent的clone返回值类型为NLComponent*。
在NLComponent中的虚拟拷贝构造函数能让实现NewLetter的(正常的)拷贝构造函数变得很容易:
class NewsLetter { public: NewsLetter(const NewsLetter& rhs); ... private: list<NLComponent*> components; }; NewsLetter::NewsLetter(const NewsLetter& rhs) { // 遍历整个rhs链表,使用每个元素的虚拟拷贝构造函数 // 把元素拷贝进这个对象的component链表。 // 有关下面代码如何运行的详细情况,请参见条款35。 for (list<NLComponent*>::const_iterator it = rhs.components.begin(); it != rhs.components.end(); ++it) { // "it" 指向rhs.components的当前元素,调用元素的clone函数, // 得到该元素的一个拷贝,并把该拷贝放到 //这个对象的component链表的尾端。 components.push_back((*it)->clone()); } }
遍历被拷贝的NewsLetter对象中的整个component链表,调用链表内每个元素对象的虚拟构造函数。我们在这里需要一个虚拟构造函数,因为链表中包含指向NLComponent对象的指针,但是我们知道其实每一个指针不是指向TextBlock对象就是指向Graphic对象。无论它指向谁,我们都想进行正确的拷贝操作,虚拟构造函数能够为我们做到这点。
以上内容基本都来自《More Effective C++》。