前言
工厂模式在C++中体现为构造函数虚拟化,即针对不同的已知对象,调用不同的构造函数,生成不同的新对象。
问题实例
假设现有一个程序负责生成新闻报道,每篇报道由文字或者图片组成:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class NLComponent {//abstract class
public:
...
};
class TextBlock:public NLComponent {
public:
...
};
class Graphic: public NLComponent {
public:
...
};
class NewsLetter {
public:
...
private:
list<NLComponent*> components;//以链表存储数据
};
classes之间的关系如下:
我们给NewsLetter class添加一个以istream作为参数的构造函数,其伪代码大致如下:1
2
3
4
5
6
7
8
9
10
11class NewsLetter {
public:
NewsLetter(istream& str);
...
};
NewsLetter::NewsLetter(istream& str){
while (str) {
...//从 str 读取下一个component对象;
...//将component对象加入链表
}
}
又或者,我们为此建立一个独立函数readComponent:1
2
3
4
5
6
7
8
9
10
11
12
13
14class NewsLetter {
public:
...
private:
//为建立下一个 NLComponent 对象从 str 读取数据,
//建立 component 并返回一个指针。
static NLComponent * readComponent(istream& str);
...
};
NewsLetter::NewsLetter(istream& str){
while (str) {
components.push_back(readComponent(str));
}
}
显然,readComponent根据str建立了一个新对象,其类型是text或者graphic.它的行为类似于构造函数,但又能构建不同的对象,因此被称为虚构造函数。其特性在于,根据不同的输入构造出不同的对象。(虚拟工厂模式)
虚拷贝构造函数
虚拷贝构造函数返回一个指针,指向调用该函数的对象的新拷贝,一般命名该函数为copyself或者clone,其大致实现细节如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class NLComponent {
public:
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const{
return new TextBlock(*this);
}
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const{
return new Graphic(*this);
}
...
};
可以看出,虚拷贝构造完全调用了拷贝构造,因此具备其拥有的一切特性,诸如引用计数之类。
值得注意的是,虚拷贝构造的一大特点:派生类重定义的虚函数不必与基类对应函数具备一致的返回类型,这也就是我们上例中返回Text*的原因。
这种虚拷贝构造函数让对象的正常拷贝构造变得很容易:1
2
3
4
5
6
7
8
9
10
11
12class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs){
for (auto it =rhs.components.begin();it != rhs.components.end();++it) {
components.push_back((*it)->clone());
}
}
虚拷贝构造函数的最大特性在于,无论对象的真正类型是什么,它都能轻易地完成拷贝。
非成员函数虚拟化
我们可能希望存在这么一个非成员函数,能够根据参数的不同类型而执行不同的行为。
以上文中的NewsLetter为例,我希望为text或graphic实现一个输出操作符,但operator<<默认把ostream&作为它的lhs,那注定了它无法变成成员函数。
解决方案是建立一个虚成员函数print,在operator<<中调用print函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class 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);
}
显然,虚拟非成员函数的实现过程如下:
- 完成一个虚成员函数
- 在非成员函数中调用虚成员函数
- 为了避免额外的调用开销,inline该虚非成员函数
总结
以上均为以单一参数完成虚拟化的实例,根据多参数完成虚拟化详见More Effective C++ 31。