25 将constructor和non-member functions虚化

本文探讨了在C++中如何使用虚拟构造器创建不同类型的对象,以及如何通过虚化非成员函数实现多态行为。通过具体示例说明了虚拟构造器在对象复制中的应用,以及如何定义虚函数来实现非成员函数的多态行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

假如你写一个软件,用来处理时事新闻,其内容由文字和图形构成。你可以把程序组织成这样:

class NTComponent{   //抽象基类,用于时事消息的组件
public:				 //其中至少含有一个纯虚函数
	...
};

class TextBlock:public NTComponent{
public:
	...				//没有内涵任何纯虚函数
};	

class Graphic : public NTComponent{
public:
	...				//没有内涵任何纯虚函数
};			

class NewsLetter{	//一份时事通讯是有一系列的NTComponent对象构成
public:
	...
private:
	list<NTComponent*> components;
};

这些class彼此之间的关系如下:

NewsLetter对象尚未开始运作的时候,可能存储于磁盘中。为了能够根据磁盘上的数据产生一份 NewsLetter,如果我们让NewsLetter有用一个constructor并用istream作为自变量,会很方便。这个constructor将从stream读取数据以便产生必要的核心数据结构:

class NewsLetter{
public:
	NewsLetter(istream& str);
	...
};

NewsLetter::NewsLetter(istream& str)
{
	while(str)
	{
		read the next component object;
		add the object to the list of 
		this newsletter's components;
	}
}

或许,如果将棘手的东西搬移到另一个名为readComponent的函数,就变成这样:

class NewsLetter{
public:
	...
private:
	static NTComponent* readComponent(istream& str);
	...                                             
};

NewsLetter::NewsLetter(istream& str)
{
	while(str)
	{
		//将readComponent返回的指针加到component list尾端
		//“push_back”是一个list member function,用来将对象
		//安插到list尾端
		components.push_back(readComponent(str));
	}
}

此时readComponent做了一些事情。它产生一个崭新的对象,获取是一个TextBlock或者是个Graphic,视读入的数据而定。由于它产生新对象,所以行为仿若constructor,但是它能够产生不同类型的对象,所以我们称之它为一个virtual constructor。所谓virtual constructor是某种函数,视其获得的输入,可产生不同的类型对象。Virtual constructors在许多情况下有用,其中之一就是从磁盘(或网盘或磁带等)读取对象信息。

有一种特别的virtual constructor——所谓的virtual copy constructor——也被广泛地运用。Virtual copy constructor 会返回一个指针,指向其调用者(某对象)的一个新副本。基于这种行为,virtual copy constructors通常以copySelf或cloneSelf命名,或者像下面一样命令为clone。很少有其他函数能够比这个函数有更直接易懂的实现方式了:

class NLComponent{
public:
	//声明 virtual copy constructor
	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);
	}
	...
};

如上,class的virtual copy constructor 就是调用真正的copy constructor而已。“copy”这层意义对这两个函数而言是一样的。如果真正的copy constructor执行的是浅复制,virtual copy constructor也一样。如果真正的copy constructor执行的是深拷贝,virtual copy constructor也一样。如果真正的copy constructor做了某些煞费苦心的动作,如 reference counting(引用计数)或copy-on-write(“写入时才复制”),virtual copy constructor也一样。

上述的手法:当derived class重新定义其base class的一个虚函数时,不再需要一定得声明与原函数相同的返回类型。如果函数的返回值是一个指针(或引用),指向一个base class。那么derived class的函数可以返回一个真正(或引用)指向base class的一个derived class。

在NLComponent中的virtual copy constructor让实现NewLetter的拷贝构造函数变得很容易:


class NewsLetter{
public:
	NewsLetter(const NewsLetter& rhs);
	...
private:
	list<NLComponent*> components;
};

NewsLetter::NewsLetter(const NewsLetter& rhs)
{
	//遍历整个rhs链表,使用每个元素的virtual copy constructor
	//把元素拷贝到对象的component链表里。
	for(list<NLComponent*>::const_iterator it = rhs.components.begin();
			it != rhs.components.end(),++it)
			{
				components.push_back((*it)->clone());
			}
}

将non-Member Functions的行为虚化

就像constructor无法真正被虚化一样,non-member functions也是。然而就像我们能够以某个函数构造出不同类型的新对象一样,我们也认为应该让non-member functions的行为视其参数的动态类型而不同。

例如:假设你希望为TextBlock和Graphic实现出output操作符,明显的一个办法就是让output操作符虚化。然而output操作符(operator<<)获得一个哦stream&作为左端自变量,因此不可能成为一个TextBlock或Graphic classes的一个member function。

(是可以的,但是你看看会发生什么事情:

class NLComponent{
public:
	//output operator的非传统声明
	virtual ostream& operator(ostream& str) const = 0;
};

class TextBlock:public NLComponent{
public:
	//virtual output operator(也是非传统模式)
	virtual ostream& operator(ostream& str);
};

class Graphic:public NLComponent{
public:
	//virtual output operator(也是非传统模式)
	virtual ostream& operator(ostream& str);
};

TextBlock t;
Graphic g;
...
t << cout;
g << cout;

Clients必须把stream对象放在“<<”符号的右手端,而那和传统的output操作符习惯不符。如果回到正常的语法形式,我们必须将operator从TextBlock和Graphic classes身上移除,但是如果这么做,我们就不再能够将它声明为virtual。)

另外一个做法就是声明一个虚函数(例如,print)作为打印之用,并在TextBlock和Graphic中定义它。但如果我们这么做,extBlock和Graphic对象的打印语法就和其他类型的打印语法不一致,因为其他类型依赖operator<<作为输出之用。

这些解法没有一个令人满意。我们真正需要的是一个名为operator<<的non-member function。展示出类似print虚函数一般的行为。这一段“需求描述”其实已经非常接近其“做法描述",是的,让我们同时定义operator<<和print,并令前者调用后者:

class NLComponent{
public:
	virtual ostream& print(ostream& str) const = 0;
};

class TextBlock:public NLComponent{
public:
	virtual ostream& print(ostream& str);
};

class Graphic:public NLComponent{
public:
	virtual ostream& print(ostream& str);
};

inline ostream& operator<<(ostream& s,const NLComponent& c)
{
	return c.print(s);
}

显然,non-member functions的虚化十分容易:写一个虚函数做实际工作,再写一个什么都不做的非虚函数,只负责调用虚函数。当然了,为了避免此巧妙安排蒙受函数调用所带来的成本,你可以将非虚函数inline化。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值