Effective C++ 学习笔记 条款12 复制对象时勿忘其每一个成分

news/2024/4/16 10:07:07/文章来源:https://blog.csdn.net/tus00000/article/details/136499303

设计良好的面向对象系统(OO-system)会将对象的内部封装起来,只留两个函数负责对象拷贝,即copy构造函数和copy assignment操作符,作者称它们为copying函数。条款5观察到编译器会在必要时候为我们的class创建copying函数,这些“编译器生成版”的行为是:将被拷对象的所有成员变量都做一份拷贝。

如果你声明自己的copying函数,意思就是告诉编译器你不喜欢缺省实现中的某些行为。编译器仿佛被冒犯似的,会以一种奇怪的方式回敬:当你的实现代码几乎必然出错时却不告诉你。

考虑一个class用来表现顾客,其中手工写出(而非由编译器创建)copying函数,使得外界对他们的调用会被志记(logged)下来:

void logCall(const std::string &funcName);    // 制造一个log entry
class Customer
{
public:// ...Customer(const Customer &rhs);Customer &operator=(const Customer &rhs);// ...private:std::string name;
};Customer::Customer(const Customer &rhs) : name(rhs.name)    // 复制rhs的数据
{logCall("Customer copy constructor");
}Customer &Customer::operator=(const Customer &rhs)
{logCall("Customer copy assignment operator");name = rhs.name;    // 复制rhs的数据return *this;    // 见条款10
}

这里每件事看起来都很好,而实际也的确很好,直到另一个成员变量加入战局:

class Date { /* ... */ };class Customer
{
public:// ...private:std::string name;Date lastTransaction;
};

这时现有的copying函数执行的是局部拷贝(partial copy):它们的确复制了顾客的name,但没有复制新添加的lastTransactoin。大多数编译器对此不出任何怨言——即使在最告警高级别中(见条款53)。这是编译器对“你自己写出copying函数”的复仇行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不告诉你。结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数(你也需要修改class的所有构造函数(见条款4和条款54)以及任何非标准形式的operator=(条款10有个例子),如果你忘记,编译器不太可能提醒你)。

一旦发生继承,可能会造成一个潜藏危机。考虑:

class PriorityCustomer : public Customer
{
public:// ...PriorityCustomer(const PriorityCustomer &rhs);PriorityCustomer &operator=(const PriorityCustomer &rhs);// ...private:int priority;
};PriorityCustomer::PriorityCustomer(const PriorityCustomer &rhs) : priority(rhs.priority)
{logCall("PriorityCustomer copy construction");
}PriorityCustomer &PriorityCustomer::operator=(const PriorityCustomer &rhs)
{logCall("PriorityCustomer copy assignment operator");priority = rhs.priority;return *this;
}

PriorityCustomer的copying函数看起来好像复制了PriorityCustomer内的每一样东西,但实际上,它只复制了PriorityCustomer声明的成员变量,但每个PriorityCustomer还内含它所继承的Customer成员变量,而那些成员变量却未被复制。PriorityCustomer的copy构造函数并没有指定实参传给其base class构造函数(即没有在PriorityCustomer的成员初值列(member initialization list)中提到Customer),因此PriorityCustomer对象的Customer成分会被不带实参的Customer构造函数(即default构造函数——必定有一个,否则无法通过编译)初始化。default构造函数将针对name和lastTransaction执行缺省的初始化动作。

以上事态在PriorityCustomer的copy assignment操作符身上只有轻微不同。它不曾企图修改其base clsss的成员变量,所以那些成员变量保持不变。

任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private(条款22),所以你无法直接访问它们,你应该让derived class的copying函数调用相应的base class函数:

PriorityCustomer::PriorityCustomer(const PriorityCustomer &rhs) : Customer(rhs),    // 调用base class的copy构造函数priority(rhs.priority)
{logCall("PriorityCustomer copy constructor");
}PriorityCustomer &PriorityCustomer::operator=(const PriorityCustomer &rhs)
{logCall("PriorityCustomer copy assignment operator");Customer::operator=(rhs);    // 对base class成分进行赋值操作priority = rhs.priority;return *this;
}

本条款题目所说的“复制每一个成分”现在应该很清楚了。当你编写一个copying函数,请确保:
1.复制所有local成员变量。

2.调用所有base class内的适当的copying函数。

这两个copying函数往往有近似相同的实现本体,这可能会诱使你让某个函数调用另一个函数以避免代码重复。这样精益求精的态度值得赞赏,但是令某个copying函数调用另一个copying函数却无法让你达到你想要的目标。

令copying assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象。这件事如此荒谬,乃至于根本没有相关语法。是有一些看似如你所愿的语法,但其实不是;也的确有些语法背后真正做了它,但它们在某些情况下会造成你的对象损坏,所以作者不打算呈现那些语法。单纯地接受这个叙述吧:你不该令copy assignment操作符调用copy构造函数。

反方向——令copy构造函数调用copy assignment操作符——同样无意义。构造函数用来初始化新对象,而assignment操作符只用于已初始化对象身上。对一个尚未构造好的对象赋值,就像在一个尚未初始化的对象身上做“只对已初始化对象才有意义”的事一样。别尝试。

如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。

请记住:
1.copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_996925.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javaWebssh在线授课辅导系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh在线授课辅导系统是一套完善的web设计系统(系统采用ssh框架进行设计开发),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用 B/S模式开发。开发环境为TOMCAT7.…

用户角色的重要性:确保财务数据安全的最佳方式

在企业的财务管理业务中,一个人几乎不可能完成所有的财务记账任务,例如设定预算、发票审批等等,至少不能有效地执行。最为明智的方式,是将这些任务分派给特定的人员,比如部门经理、财务经理或者销售、市场人员等等。 但…

设计模式(二)单例模式

单例模式:确保一个类只有一个实例,并提供了全局访问点;主要是用于控制共享资源的访问; 单例模式的实现分为懒汉式和饿汉式。 懒汉式单例在需要时才会创建,而饿汉式单例则在类加载时立即创建实例; 单例模…

【airtest】自动化入门教程(二)airtest操作

目录 一、touch 二、wait 三、swipe 四、exists 五、text 六、keyevent 七、snapshot 八、sleep 九、断言 9.1 assert_exists 9.2 assert_not_exists 9.3 assert_equal 9.4 assert_not_equal 前言:本文主要针对aritest部分的基础操作,aritest是一个跨平…

Selenium上传文件有多少种方式?不信你有我全

Selenium 封装了现成的文件上传操作。但是随着现代前端框架的发展,文件上传的方式越来越多样。而有一些文件上传的控件,要做自动化控制会更复杂一些,这篇文章主要讨论在复杂情况下,如何通过自动化完成文件上传 1.input 元素上传文…

前端从普通登录到单点登录(SSO)

随着前端登录场景的日益复杂化和技术思想的不断演进,前端在登录方面的知识结构变得越来越复杂。对于前端开发者来说,在日常工作中根据不同的登录场景提供合适的解决方案是我们的职责所在,本文将梳理前端登录的演变过程。 1、无状态的HTTP H…

蜘蛛池是什么意思,怎么生成蜘蛛池

蜘蛛池是由自然界中的蜘蛛群落构成的一个小生态系统,也是身处自然界中的游客们可以在风雨中体验到最贴近自然气息的地方。 点开我主页面 Baidu蜘蛛的作用: 引蜘蛛逐渐收录,降权引蜘蛛可以疗伤,排名/收录不稳定,没有收…

Transformer中的FeedForward

Transformer中的FeedForward flyfish class PoswiseFeedForwardNet(nn.Module):def __init__(self, d_ff2048):super(PoswiseFeedForwardNet, self).__init__()# 定义一维卷积层 1,用于将输入映射到更高维度self.conv1 nn.Conv1d(in_channelsd_embedding, out_ch…

3.7作业

一 1)应用层 负责处理不同应用程序之间的通信,需要满足提供的协议,确保数据发送方和接收方的正确 应用层提供的协议: (2)表示层 负责网络中通信的数据的编码和格式,确保通信过程中…

rust入门(1)创建项目

安装 vscode 安装插件 rust-analyzerNative Debug vscode 配置自动格式化代码 settings.json{"editor.defaultFoldingRangeProvider": null,"[rust]": {"editor.defaultFormatter": "rust-lang.rust-analyzer", // Makes the magi…

基于AFDPF主动频率偏移法的孤岛检测Simulink仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于AFDPF主动频率偏移法的孤岛检测Simulink仿真。 2.系统仿真结果 3.核心程序与模型 版本:MATLAB2022a 36 4.系统原理简介 在分布式发电系统中,孤…

【实战】K8S集群部署nacos并接入Springcloud项目容器化运维

文章目录 前言Nacos集群搭建Spring cloud配置nacos将Springcloud项目部署在k8s写在最后 前言 相信很多同学都开发过以微服务为架构的系统,开发微服务必不可少要使用注册中心,比如nacos\consul等等。当然在自动化运维流行的今天,我们也会将注…

windows部署ruoyi-vue-pro

前提 安装java 安装maven 安装redis mysql 源代码下载 后端 ruoyi-vue-pro 前端 yudao-ui-admin-vue3 后端项目 配置maven 导入数据 CREATE DATABASE ruoyi_vue_pro;修改mysql连接配置 修改redis 打包项目 mvn clean install package -Dmaven.test.skiptrue启动YudaoSe…

QEMU调试——通过获取设备树(dtb文件)查询开发板的外设地址信息

1、适用场景 使用qemu时,想快速知道开发板的地址空间映射情况,特别是某些外设控制器的寄存器基地址 2、查询QEMU支持的开发板 qemu-system-riscv32.exe -M ? 3、获取开发板对应的dtb文件 1、qemu-system-riscv32.exe -M nuclei_evalsoc 2、dumpdtb nucl…

C#,无监督的K-Medoid聚类算法(K-Medoid Algorithm)与源代码

1 K-Medoid算法 K-Medoid(也称为围绕Medoid的划分)算法是由Kaufman和Rousseeuw于1987年提出的。中间点可以定义为簇中的点,其与簇中所有其他点的相似度最小。 K-medoids聚类是一种无监督的聚类算法,它对未标记数据中的对象进行聚…

@ResponseStatus

目录 概述: 用途: 参数: 注意事项: 自定义异常类: 底层原理: 概述: 在 Spring MVC 中,我们有很多方法来设置 HTTP 响应的状态码其中最直接的方法:使用 ResponseSt…

基于Java的校园失物招领管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系统公告模块2.4 感谢留言模块 三、界面展示3.1 登录注册3.2 招领模块3.3 寻物模块3.4 公告模块3.5 感谢留言模块3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 校园失物招领…

Bililive-go 实现直播自动监控录制

前言 最近有直播录制的需求,但是自己手动录制太麻烦繁琐,于是用了开源项目Bililive-go进行全自动监控录制,目前这个项目已经有3K stars了 部署 为了方便我使用了docker compose 部署 version: 3.8 services:bililive:image: chigusa/bilil…

通过修改host文件来访问GitHub

前言: 由于国内环境的原因,导致我们无法流畅的访问GitHub,。 但是我们可以采取修改host文件来实现流畅访问。 缺点:需要不定时的刷新修改。 操作流程 一、查询IP地址 以下地址可以查询ip地址 http://ip.tool.chinaz.com/ htt…

曾桂华:车载座舱音频体验探究与思考| 演讲嘉宾公布

智能车载音频 I 分论坛将于3月27日同期举办! 我们正站在一个前所未有的科技革新的交汇点上,重塑我们出行体验的变革正在悄然发生。当人工智能的磅礴力量与车载音频相交融,智慧、便捷与未来的探索之旅正式扬帆起航。 在驾驶的旅途中&#xff0…