1. Virtual constructor
原则上构造函数不能为虚:虚函数用于实现"因类型而异的行为",也就是根据指针或引用所绑定对象的动态类型而调用不同实体,但构造函数用于构造对象,在对象构造之前自然没有动态类型的概念,虚与非虚也就无从谈起.所谓的的virtual-constructor实际上是"仿virtual-constructor",它本质上不是constructor,但能够产生不同类型的对象,从而实现"virtual-constructor"的功能,考虑以下继承层次用于管理新闻报道:
class NLComponent { //抽象基类,用于实时通讯的组件,其中内含至少一个纯虚函数 public: ... }; class TextBlock: public NLComponent { public: ... // 没有内含任何纯虚函数 }; class Graphic: public NLComponent { public: ... // 没有内含任何纯虚函数 }; class NewsLetter { //一份实时通信是由一系列NLCompnent对象组成 public: ... private: list<NLComponent*> components; };
NewsLetter可能存储于磁盘中,为了从磁盘中读取NewsLetter对象到内存中,它可能有一个接受istream&参数的构造函数:
class NewsLetter { public: NewsLetter(istream& str); ... private: list<NLCompnent*> compnents; ... }; NewsLetter::NewsLetter(istream& str){ while (str) { read the next compnent object from str; add the object to the list of this; newsletter's compnents; } }
NewsLetter(istream& str)又可以将其主要功能交由一个readCompnent函数实现:
class NewsLetter { public: NewsLetter(istream& str); ... private: //从str读取下一个NLCompnent的数据,产生组件(compnent),并返回一个指针指向它 static NLCompnent* readCompnent(istream& str); list<NLCompnent*> compnents; ... }; NewsLetter::NewsLetter(istream& str){ while (str) { //将readCompnent返回的指针加到compnents list尾端 compnents.push_back(readCompnent(str)); } }
在这里readCompnent产生一个新的NLCompnent派生对象(或是TextBlock或是Graphic),并返回一个NLCompnent指针,由于它能够产生不同类型对象,因此称它为一个virtual constructor.Virtual constructor在很多情况下都很有用,其中之一就是从磁盘(或网络或磁带)读取对象信息.
一种特殊的virtual constructor——copy virtual constructor,它返回一个指针指向其调用者的新副本,指针的动态类型由调用它的对象的类型决定.TextBlock和Graphic的copy virtual constructor的可以像这样:
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); } ... };
这样就clone就实现了virtual copy constructor的功能.
注意,虽然derived class重新定义base class的虚函数时,声明必须要与base class中相同,但如果函数返回类型是指向base class的指针(或引用),那么derived class可以返回指向其derived class的指针(或引用).因此以上clone虽然是虚函数,但其返回的指针类型可以不同.
基于以上NLCompnent的virtual copy constructor,可以为NewsLetter实现(正常的)copy constructor:
class NewsLetter { public: NewsLetter(const NewsLetter& rhs); ... private: list<NLComponent*> components; }; NewsLetter::NewsLetter(const NewsLetter& rhs){ //遍历rhs的list,运用每个元素的virtual copy constructor将元素复制到此对象的compnents list中. for (list<NLComponent*>::const_iterator it =rhs.components.begin();it != rhs.components.end();++it) //it指向rhs.compnents的目前元素,调用该元素的clone函数取得一个副本并加到本对象的compnents list的尾端 components.push_back((*it)->clone()); }
2. virtual non-member function.
正如constructors无法被虚化,non-member function原则上也无法被虚化——它连成员函数都不是.考虑要为TextBlock和Graphic实现<<操作符,要使<<对TextBlock和Graphic实现不同的行为,直接的思路就是将<<虚化,但实际上这无法实现:<<的第一个操作数是ostream&,也就是说<<要作为member function,就只能成为ostream类的成员,但这是不可能的.因此<<只能为non-member函数,这是可以采取和virtual constructor类似的策略实现virtual non-member function:
class NLComponent { public: virtual ostream& print(ostream& s) const = 0; ... }; class TextBlock: public NLComponent { bbs.theithome.com public: virtual ostream& print(ostream& s) const; ... }; class Graphic: public NLComponent { public: virtual ostream& print(ostream& s) const; ... }; inline ostream& operator<<(ostream& s, const NLComponent& c){ return c.print(s); }
non-member的虚化和virtual copy constructor的实现类似:写一个虚函数做实际工作,再一个非虚函数调用该虚函数.此外,为了避免函数调用的额外成本(毕竟operator<<只有一句,不值得将压弹栈的成本),可以将非虚函数inlin化.
到此为止,可以做到让non-member copy constructor视其某个自变量而虚化,要实现根据一个以上自变量的虚化,见条款31.