1、虚拟构造函数:
应用场景:根据从磁盘读取的不同对象信息,构造不同对象。
//.h
class NLComponent { //用于 newsletter components
public: // 的抽象基类
... //包含至少一个纯虚函数
};
class TextBlock: public NLComponent {
public:
... // 不包含纯虚函数
};
class Graphic: public NLComponent {
public:
... // 不包含纯虚函数
};
class NewsLetter { // 一个 newsletter 对象
public: // 由 NLComponent 对象
... // 的链表组成
NewsLetter(istream& str);
private:
list<NLComponent*> components;
// 为建立下一个 NLComponent 对象从 str 读取数据,
// 建立 component 并返回一个指针。
static NLComponent * readComponent(istream& str);
};
//.cpp
NewsLetter::NewsLetter(istream& str)
{
while (str) {
// 把 read Component 返回的指针添加到 components 链表的最后,
// "push_back" 一个链表的成员函数,用来在链表最后进行插入操作。
components.push_back(readComponent(str));
}
}
注:read Component()
的实现可详见《汤姆.斯旺 C++编程秘诀》。
虚拟构造函数本质:只是根据参数类型不同调用不同的构造函数。
2、虚拟拷贝构造函数:
应用场景:根据不同对象自动调用各自不同的复制构造函数。自动调用过程给人以虚函数调用的感觉。
//.h
class NLComponent { //抽象类
public:
// declaration of virtual copy constructor
virtual NLComponent* clone() const = 0;
...
};
class TextBlock: public NLComponent { //派生类1
public:
关键代码段:
virtual TextBlock * clone() const // virtual copy
{ return new TextBlock(*this); } // constructor
...
};
class Graphic: public NLComponent { //派生类2
public:
virtual Graphic * clone() const // virtual copy
{ return new Graphic(*this); } // constructor
...
};
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);
...
private:
list<NLComponent*> components;
};
//.cpp
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
// 遍历整个 rhs 链表,使用每个元素的虚拟拷贝构造函数
// 把元素拷贝进这个对象的 component 链表。
// 有关下面代码如何运行的详细情况,请参见条款 M35.
for (list<NLComponent*>::const_iterator it =
rhs.components.begin();
it != rhs.components.end();
++it) {
// "it" 指向 rhs.components 的当前元素,调用元素的 clone 函数,
// 得到该元素的一个拷贝,并把该拷贝放到
// 这个对象的 component 链表的尾端。
components.push_back(it->clone());
}
}
类的虚拟复制构造函数本质:只是通过编写其他虚函数,在其中调用它们真正的复制构造函数。
3、虚拟化非成员函数:
如为 TextBlock 和 Graphic 对象实现一个输出操作符:
a) 不好的做法:将cout作为参数重载<<
//.h
class NLComponent {
public:
// 对输出操作符的不寻常的声明
virtual ostream& operator<<(ostream& str) const = 0;
...
};
class TextBlock: public NLComponent {
public:
// 虚拟输出操作符(同样不寻常)
virtual ostream& operator<<(ostream& str) const;
};
class Graphic: public NLComponent {
public:
// 虚拟输出操作符 (同样不寻常)
virtual ostream& operator<<(ostream& str) const;
};
//.cpp
TextBlock t;
Graphic g;
...
t << cout; // 通过 virtual operator<< 把 t 打印到 cout 中。 不寻常的语法
g << cout; // 与常规cout<<t; 不同
b) 不好的做法:自定义输出函数如print
//.h
class NLComponent {
public:
// 自定义输出函数不好看
virtual ostream& print(ostream& s) const = 0;
...
};
class TextBlock: public NLComponent {
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);
}
//.cpp
TextBlock t;
Graphic g;
...
t.print();
g.print();
c) 好的做法:虚拟化非成员函数
//.h
class NLComponent {
public:
virtual ostream& print(ostream& s) const = 0;
...
};
class TextBlock: public NLComponent {
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);
}
//.cpp
TextBlock t;
Graphic g;
...
cout<<t<<g; //好的做法
虚拟化非成员函数本质:编写一个虚函数来完成工作,然后再写一个非虚函数,它什么也不做只是调用这个虚拟函数。为了避免这个句法花招引起函数调用开销,可以内联这个非虚拟函数。
总结:
1、通过虚拟构造函数,可以实现根据参数类型不同调用不同的构造函数。
2、通过类的虚拟复制构造函数,实现根据不同对象自动调用各自不同的复制构造函数。过程为编写其他虚函数,在其中调用它们真正的复制构造函数,自动调用过程给人以虚函数调用的感觉。
3、通过虚拟化非成员函数,实现根据不同对象自动调用各自不同的函数。过程为编写一个虚函数来完成工作,然后再写一个非虚函数,它什么也不做只是调用这个虚拟函数。