首页 > 编程语言 >More Effective C++之技术Techniques,Idioms,Patterns_条款25

More Effective C++之技术Techniques,Idioms,Patterns_条款25

时间:2024-12-23 23:03:14浏览次数:11  
标签:25 NewsLetter Effective Idioms virtual public constructor copy class

More Effective C++之技术Techniques,Idioms,Patterns

    本章描述C++程序员常常遭遇的一些问题的解决办法,这些解法都已获得证明。本书把这样的解法称为techniques(技术),也有称为idioms(惯用手法)或patterns(模式)。不论如何称呼它们,当每天与软件开发过程中的各种小冲突搏斗时,本章提供的信息可以带给我们很多帮助。

条款25:将constructor和non-member function虚化

constructor虚化

    第一次面对“virtual constructors”时,似乎不觉得有什么道理可言。是的,当我们手上有一个对象的pointer或reference,而不着知道该对象的真正类型是什么的时候,我们会调用virtual function(虚函数)以完成“因类型而异的行为”。当我们尚未获得对象,但已经确知需要什么类型的时候,我们会调用constructor以构造对象。那么,什么是virtual constructor呢?
    很简单,虽然virtual constructors似乎有点荒谬,但它们很有用。假设我们写了一个软件,用来处理时事新闻,其内容由文字和图形构成。我们可以把程序组织成这样:

class NLComponent {										// 抽象基类,用于时事消息的组件,其中内含一个纯虚函数
public:
	...
};
class TextBlock : public NLComponet {
public:
	...													// 没有内含纯虚函数
};
class Graphic: public NLComponent {
public:
	...													// 没有内含纯虚函数
};
class NewsLetter {										// 一份时事通信是由一系列的NLComponent对象构成的。
public:
	...
private:
	list<NLComponent*> componets;
};

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

    NewsLetter所使用的list class由Standard Template Library提供,后者是C++标准程序库的一部分。list对象的行为就像双向链表(double linked lists)——尽管它们不一定得用双向链表实现。
    NewLetter对象尚未开始运作的时候,可能存储于磁盘中。为了能够根据磁盘上的数据产出一份Newsletter,如果我们让NewsLetter拥有一个constructor并用istream作为自变量,会很方便。这个constructor将从stream读取数据以便产生必要的核心数据结构:

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

    此constructor的伪代码(pseudo code)可能看起来像这样:

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

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

class NewsLetter {
public:
	...
private:
	// 从str读取下一个NLComponent的数据,
	// 产生组件(component),并返回一个指针指向它。
	static NLComponent* readComponent(istream& str);
	...
};
NewsLetter::NewsLetter(istream& str) {
	while(str) {
		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执行的是浅复制(shallow copy),virtual copy constructor也一样。如果真正的copy constructor执行的是深复制(deep copy),virtual copy constructor也一样。如果真正的copy constructor做了某些煞费苦心的动作,如reference-counting(引用技术)或copy-on-write(“写入时才复制”),virtual copy constructor也一样。因为时函数调用,保持了其行为的一致性。
    注意上述实现手法乃利用“虚函数之返回类型”规则中的一个宽松点,那是晚些才被接纳的一个规则。当derived class重新定义其base class的一个虚函数时,不再需要一定得声明与原本相同的返回类型。如果函数的返回类型是个指针(或reference),指向一个base class,那么derived class的函数可以返回一个指针(或reference),指向该base reference的一个derived class。这并不会造成C++的类型系统门户洞开,却可准确声明出像virtual copy constructor这样的函数。这也就是为什么即使NLComponent的clone函数的返回类型是NLComponent*,TextBlock的clone函数却可以返回TextBlock*,而Graphic的clone函数可以返回Graphic*的原因。
    NLComponent拥有一个virtual copy constructor,于是我们现在可以为NewsLetter轻松实现一个(正常的)copy constructor:

class 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对象的component list,并针对其中的每一个组件(component)调用其virtual copy constructor即可。在这里,我们需要一个virtual copy constructor,因为这个component list内含NLComponent对象指针,但我们知道每个指针真正指向的是一个TextBlock或是Graphic。我们希望无论指针真正指向什么,我们都可以复制它,virtual copy constructor可以达到这个目标。

将Non-Member Functions的行为虚化

    就像constructors无法真正被虚化一样,non-member functions也是。然后就像我们认为应该能够以某个函数构造出不同类型的新对象一样,我们也认为应该可以让non-member-functions的行为视其参数的动态类型而不同。举个例子,假设我们希望为TextBlock和Graphic实现出ouput操作符,显而易见的一个做法是让ouput操作符虚化。然而output操作符(operator<<)获得一个ostream&作为左端自变量,因此它不可能成为TextBlock或Graphic classes的一个memeber function。尝试定义如下:

class NLComponent {
public:
	// output operator的非传统声明
	virtual ostream& operator <<(ostream& str) const = 0;
	...
};
class TextBlock : public NLComponent {
public:
	// output operator(也是打破传统)。
	virtual ostream& operator <<(ostream& str) const ;
};
class Graphic : public NLComponent {
public:
	// output operator(也是打破传统)。
	virtual ostream& operator <<(ostream& str) const ;
};
TextBlock t;
Graphic g;
...
t << cout;											// 通过operator << ,在cout身上打印t,注意cout不是在前面
g << cout;											// 通过operator << ,在cout身上打印g,注意cout不是在前面

    Clients必须把stream对象放在"<<"符号的右端,而那和传统的output操作符习惯不符。如果要回到正常的语法形式,我们必须将operator<<从TextBlock和Graphic classes身上移除,但如果那么做,我们就不再能够将它声明为virtual。
    另一种做法是声明一个虚函数(例如,print)作为打印之用,并在TextBlock和Graphic中定义它。但如果我们这么做,TextBlock和Graphic对象的打印语法就和其他类型的打印语法不一致,因为其他类型都依赖operator<<作为输出之用。
    这些解法没有一个令人满意。我们真正需要的是一个名为operator<<的non-memeber function,展现出类似print虚函数一般的行为。这一段“需求描述”其实已经非常接近其“做法描述”,是的,让我们同时定义operator<<和print,并令前者调用后者:

class NLComponent {
public:
	virtual ostream& print(ostream& str) const = 0;
	...
};
class TextBlock : public NLComponent {
public:
	virtual ostream& print(ostream& str) const ;
};
class Graphic : public NLComponent {
public:
	virtual ostream& print(ostream& str) const ;
};
inline ostream& operator<<(ostream &s, const NLComponent& c) 
{ return c.print(s); }

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

学习心得
    本主题介绍了构造函数和非成员函数的“虚化”技术。构造函数虚化分为两部分:一是类似于工厂函数,使用静态函数构造产生对象,二是利用clone虚函数,内部调用复制构造函数,完成虚拟拷贝构造的嫁接(因为事实上调用clone时并不需要当前类的名字来显示调用复制构造函数)。非成员函数的虚化,主要是在例如输出操作符<<不适合定义为成员函数时,也可以通过内部定义一个虚函数,然后在非成员函数中进行一次中转(调用类的虚函数),看上去将非成员函数也进行了虚化一般。此两种方法,不失为我们在编码过程中的有效参考。

标签:25,NewsLetter,Effective,Idioms,virtual,public,constructor,copy,class
From: https://blog.csdn.net/chnming1987/article/details/144676366

相关文章

  • 2024-2025 集训队互测 Round 8 - 熟练
    约定任选一个点为根,使其变为一棵有根树。结论:答案就是被覆盖次数最多的点被覆盖的次数。考虑证明:我们每次令被覆盖次数最多的点为关键点,然后考虑选出若干条路径,使得其互不相交,并且包含所有关键点,并将其染成一种颜色并把它们从图中删掉,不再存在。可以证明必定存在一种选的方案。......
  • 【Adobe Illustrator 2025下载与安装】
    1、安装包我用夸克网盘分享了「Illustrator2025」,链接:下载地址2、安装教程(安装前关闭系统防护)1)       下载软件安装包,双击Set-up.exe安装  2)       修改安装目录,点击继续  3)       安装完成,点击启动  4)       启动程序......
  • 2025年最新分享Win11专业工作站版永久密钥
    Windows11专业工作站版是Windows11家族中的顶级版本,专为满足需要强大性能和高级功能的高端用户和企业而设计。它在Windows11专业版的基础上进行了增强,提供了更强大的硬件支持、更高的性能和更高级的功能,以应对最苛刻的工作负载。主要特性和优势:更强大的硬件支持:多......
  • 2025最新分享Win11专业版永久密钥
    Windows11专业版是微软面向企业用户和专业人士推出的操作系统,在家庭版的基础上增加了许多高级功能,旨在提升工作效率、增强安全性,并提供更灵活的管理选项。主要特点:强大生产力工具:虚拟桌面、Snap布局、改进的任务栏等功能,帮助用户更高效地管理多个任务和窗口。增强安全性:B......
  • BOE(京东方)“向新2025”年终媒体智享会落地成都 持续创新引领产业步入高价值增长新纪元
    12月20日,BOE(京东方)“向新2025”年终媒体智享会的脚步从上海延伸至成都。川渝之地,作为BOE(京东方)产业生态战略布局中的关键一子,此刻再度成为行业瞩目的焦点。本次活动全面回溯了BOE(京东方)在2024年多个关键领域斩获的斐然佳绩,深入剖析了六大维度构建的“向新”发展格局,系统展示了技......
  • springboot实践类课程教学辅助系统-毕业设计源码25964
    目 录摘要1绪论1.1研究背景1.2 研究意义1.3论文结构与章节安排2系统分析2.1可行性分析2.2系统流程分析2.2.1登录流程2.2.2数据删除流程2.3 系统功能分析2.4系统用例分析2.5本章小结3 系统总体设计3.1系统架构设计3.2系统功能模块......
  • springboot停车管理系统-毕业设计源码25879
    目 录摘要1绪论1.1选题背景与意义1.2开发现状1.3论文结构与章节安排2 停车管理系统系统分析2.1可行性分析2.1.1技术可行性分析2.1.2 经济可行性分析2.1.3法律可行性分析2.2系统功能分析2.2.1功能性分析2.2.2非功能性分析2.3 系统用......
  • SSM校园疫情管理系统-毕业设计源码25914
    摘要随着新冠疫情的全球蔓延,校园疫情管理成为了教育领域的重要议题。为了应对这一挑战,本文提出并实现了一个基于微信小程序的校园疫情管理系统。该系统旨在通过数字化手段,提高校园疫情管理的效率和安全性,保障师生的健康与安全。该系统基于微信小程序框架开发,利用其便捷性......
  • 2025年转行进入网络安全领域薪资及工作安排与前景如何
     如果你计划在2025年转行到网络安全领域,以下是一些建议,可以帮助你顺利过渡并打下坚实的基础:1.薪资情况初级职位(0-3年经验)薪资范围:大约8k-15k/月(根据地区、公司规模和工作内容有所不同)。职位类型:包括网络安全运维、信息安全管理员、安全工程师等。工作内容:监控网络、......
  • 这家语音 AI 公司新融资 2700 万美元,并预测了 2025 年语音技术趋势
    12月13日,语音AI初创公司Cartesia宣布完成了新一轮2700万美元的融资,由知名风投机构IndexVentures领投。 Cartesia由KaranGoel和AlbertGu联合创立,专注于开发基于状态空间模型(SSM)的创新架构,该架构以其卓越的计算效率和实时处理能力而著称。Cartesia的核心产......