将构造函数和非成员函数“虚拟化”
在讨论内容之前先看下面的一个程序,它用来进行新闻报道工作,每一条新闻报道都由文字或图片组成。
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent { //用于 newsletter components
public: // 的抽象基类
... //包含至少一个纯虚函数
};
class TextBlock: public NLComponent {
public:
... // 不包含纯虚函数
};
class Graphic: public NLComponent {
public:
... // 不包含纯虚函数
};
class NewsLetter { // 一个 newsletter 对象
public: // 由NLComponent 对象
... // 的链表组成
private:
list<NLComponent*> components;
};
</SPAN>
class NLComponent { //用于 newsletter components
public: // 的抽象基类
... //包含至少一个纯虚函数
};
class TextBlock: public NLComponent {
public:
... // 不包含纯虚函数
};
class Graphic: public NLComponent {
public:
... // 不包含纯虚函数
};
class NewsLetter { // 一个 newsletter 对象
public: // 由NLComponent 对象
... // 的链表组成
private:
list<NLComponent*> components;
};
类结构:
NewLetter在不运行时存储在磁盘上,所以让它的构造函数带有istream参数,可以从流中读取信息。
class NewsLetter {
public:
NewsLetter(istream& str);
...
};
伪代码:
NewsLetter::NewsLetter(istream& str)
{
while (str) {
从str读取下一个component对象;
把对象加入到newsletter的 components对象的链表中去;
}
}
或者把这一读数据的功能单独抽象出来,弄一个readComponent,例如
[cpp]
<SPAN style="FONT-SIZE: 18px">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));//在这调用
}
}</SPAN>
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根据读取的数据建立一个新的对象,由于它能够建立新对象,所以行为与构造函数相似,并且它能根据输入的不同建立不同类型的对象,因此我们称它为虚拟构造函数。
虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象。虚拟构造函数在很多场合下都有用处,从磁盘(或者通过网络连接,或者从磁带机上)读取对象信息只是其中的一个应用。
还有一种特殊种类的虚拟构造函数――虚拟拷贝构造函数――也有着广泛的用途。虚拟拷贝构造函数能返回一个指针,指向调用该函数的对象的新拷贝。因为这种行为特性,虚拟拷贝构造函数的名字一般都是copySelf,cloneSelf或者是象下面这样就叫做clone。很少会有函数能以这么直接的方式实现它:
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent {
public:
// 声明一个虚拟拷贝构造函数
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy
{ return new TextBlock(*this); } // constructor
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy
{ return new Graphic(*this); } // constructor
...
};</SPAN>
class NLComponent {
public:
// 声明一个虚拟拷贝构造函数
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy
{ return new TextBlock(*this); } // constructor
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy
{ return new Graphic(*this); } // constructor
...
};
正如我们看到的,类的虚拟拷贝构造函数只是调用它们真正的拷贝构造函数。因此“拷贝”的含义与真正的拷贝构造函数相同。如果真正的拷贝构造函数只做了简单的拷贝,那么虚拟拷贝构造函数也做简单的拷贝。如果真正的拷贝构造函数做了全面的拷贝,那么虚拟拷贝构造函数也做全面的拷贝
注意上面的代码实现采用了较为宽松的虚拟函数返回值类型规则。被派生类重定义的虚函数不用必须与基类的虚拟函数具有一样的返回值。如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)。这不是C++的类型检查上的漏洞,它使得有可能声明象虚拟构造函数这样的函数。这就是为什么TextBlock的clone函数能够返回TextBlock*和Graphic的clone能够返回Graphic*的原因,即使NLComponent的clone返回值类型为NLComponent*。
在NLComponent中的虚拟拷贝构造函数能让实现NewLetter的(正常的)拷贝构造函数变得很容易:
[cpp]
<SPAN style="FONT-SIZE: 18px">class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);//拷贝构造函数
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
// 遍历整个rhs链表,使用每个元素的虚拟拷贝构造函数
// 把元素拷贝进这个对象的component链表。
for (list<NLComponent*>::const_iterator it =
rhs.components.begin();
it != rhs.components.end();
++it) {
// "it" 指向rhs.components的当前元素,调用元素的clone函数,
// 得到该元素的一个拷贝,并把该拷贝放到
// 这个对象的component链表的尾端。
components.push_back((*it)->clone());
}
}</SPAN>
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);//拷贝构造函数
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
// 遍历整个rhs链表,使用每个元素的虚拟拷贝构造函数
// 把元素拷贝进这个对象的component链表。
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对象。无论它指向谁,我们都想进行正确的拷贝操作,虚拟构造函数能够为我们做到这点。
虚拟化非成员函数
就像构造函数不能成为虚函数一样,非成员函数也不能成为真正的虚函数。但是,既然一个函数能够构造出不同类型的新对象是可以理解的,那么同样也存在这样的非成员函数,可以根据其参数的动态类型
补充:软件开发 , C++ ,