[翻译] Effective C++, 3rd Edition, Item 12: 拷贝一个 object(对象)的所有 parts(构件)

C++编程语言特别版中文第三版(part1, total 2 parts) 一本很好的C++书籍,看了让人受益匪浅。google了很久才找来的。推荐大家看一下。 立即下载

Item 12: 拷贝一个 object(对象)的所有 parts(构件)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.youkuaiyun.com/fatalerror99/

在设计良好的 object-oriented systems(面向对象系统)中,封装了 object(对象)的内部构件,只有两个拷贝 objects(对象)的函数:被恰当地称为 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。我们将它们统称为 copying functions(拷贝函数)。Item 5 讲述了如果需要,编译器会生成 copying functions(拷贝函数),而且阐明了编译器生成的版本正像你所期望的:它们拷贝被拷贝 object(对象)的全部数据。

当你声明了你自己的 copying functions(拷贝函数),你就是在告诉编译器你不喜欢缺省实现中的某些东西。编译器对此好像怒发冲冠,而且它们会用一种古怪的方式报复:当你的实现几乎可以确定是错误的时,它们偏偏不告诉你。

考虑一个代表 customers(顾客)的 class(类),这里的 copying functions(拷贝函数)是手写的,以便将对它们的调用记入日志:

void logCall(const std::string& funcName);       // make a 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)                                 // copy rhs's data
{
  logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
  logCall("Customer copy assignment operator");

  name = rhs.name;                               // copy rhs's data

  return *this;                                  // see Item 10
}

这里的每一件事看起来都不错,实际上也确实不错——直到 Customer 中加入了其它 data member(数据成员):

class Date { ... };       // for dates in time

class Customer {
public:
  ...                     // as before

private:
  std::string name;
  Date lastTransaction;
};

在这里,已有的 copying functions(拷贝函数)只进行了 partial copy(部分拷贝):它们拷贝了 customers(顾客)的 name,但没有拷贝他的 lastTransaction。然而,大多数编译器即使是在 maximal warning level(最高警告级别)(参见 Item 53),对此也是一声不吭。这是它们在对你自己写 copying functions(拷贝函数)进行报复。你拒绝了它们写的 copying functions(拷贝函数),所以即使你的代码是不完善的,它们也不告诉你。结论显而易见:如果你为一个 class(类)增加了一个 data member(数据成员),你必须确保你也更新了 copying functions(拷贝函数)。(你还必须更新 class(类)中的全部 constructors(构造函数)(参见 Items 4 和 45)以及任何非标准形式的 operator=(Item 10 给出了一个例子)。如果你忘记了,编译器未必会提醒你。)

这个问题最为迷惑人的情形之一是它会通过 inheritance(继承)发生。考虑:

class PriorityCustomer: public Customer {                  // a derived class
public:
   ...
   PriorityCustomer(const PriorityCustomer& rhs);
   PriorityCustomer& operator=(const PriorityCustomer& rhs);
   ...

private:
   int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
  logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
  logCall("PriorityCustomer copy assignment operator");

  priority = rhs.priority;

  return *this;

}

PriorityCustomer 的 copying functions(拷贝函数)看上去好像是拷贝了 PriorityCustomer 中的每一样东西,但是再看一下。是的,它拷贝了 PriorityCustomer 声明的 data member(数据成员),但是每个 PriorityCustomer 还包括一份它从 Customer 继承来的 data members(数据成员)的拷贝,而那些 data members(数据成员)根本没有被拷贝!PriorityCustomer 的 copy constructor(拷贝构造函数)没有指定传递给它的 base class constructor(基类构造函数)的参数(也就是说,在它的 member initialization list(成员初始化列表)中没有提及 Customer),所以,PriorityCustomer object(对象)的 Customer 部分被 Customer 的不取得 arguments(实参)的 constructor(构造函数)—— default constructor(缺省构造函数)初始化。(假设它有,如果没有,代码将无法编译。)那个 constructor(构造函数)为 name 和 lastTransaction 进行一次 default initialization(缺省初始化)。

对于 PriorityCustomer 的 copy assignment operator(拷贝赋值运算符),情况只是稍微有些不同。它不会试图用任何方法改变它的 base class data members(基类数据成员),所以它们将保持不变。

无论何时,你打算自己为一个 derived class(派生类)写 copying functions(拷贝函数)时,你必须注意同时拷贝 base class parts(基类构件)。当然,那些构件一般是 private(私有)的(参见 Item 22),所以你不能直接访问它们。derived class(派生类)的 copying functions(拷贝函数)必须调用与它们相对应的 base class functions(基类函数):

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:    Customer(rhs),                   // invoke base class copy ctor
  priority(rhs.priority)
{
  logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
  logCall("PriorityCustomer copy assignment operator");

  Customer::operator=(rhs);           // assign base class parts
  priority = rhs.priority;

  return *this;
}

本 Item 标题中的 "copy all parts" 的含义现在应该清楚了。当你写一个 copying functions(拷贝函数),需要保证(1)拷贝所有 local data members(局部数据成员)以及(2)调用所有 base classes(基类)中的适当的 copying function(拷贝函数)。

实际上,两个 copying functions(拷贝函数)经常有相似的函数体,而这一点可能吸引你试图通过用一个函数调用另一个来避免 code duplication(代码重复)。你希望避免 code duplication(代码重复)的愿望值得肯定,但是用一个 copying functions(拷贝函数)调用另一个来实现它是错误的。

用 copy assignment operator(拷贝赋值运算符)调用 copy constructor(拷贝构造函数)是没有意义的,因为你这样做就是试图去构造一个已经存在的 object(对象)。这太荒谬了,甚至没有一种语法来支持它。有一种语法看起来好像能让你这样做,但实际上你做不到,还有一种语法采用迂回的方法这样做,但它们在某种条件下会对破坏你的 object(对象)。所以我不打算给你看任何那样的语法。无条件地接受这个观点:不要用 copy assignment operator(拷贝赋值运算符)调用 copy constructor(拷贝构造函数)。

尝试一下另一种相反的方法——用 copy constructor(拷贝构造函数)调用 copy assignment operator(拷贝赋值运算符)——这同样是荒谬的。一个 constructor(构造函数)初始化新的 objects(对象),而一个 assignment operator(赋值运算符)只能应用于已经初始化过的 objects(对象)。借助构造过程给一个 object(对象)assignment(赋值)将意味着对一个 not-yet-initialized object(尚未初始化的对象)做一些事,而这些事只有用于 initialized object(已初始化对象)才有意义。简直是胡搞!禁止尝试。

作为一种代替,如果你发现你的 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)有相似的代码,通过创建一个供两者调用的第三方 member function(成员函数)来消除重复。这样一个函数一般是 private(私有)的,而且经常叫做 init。这一策略是在 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)中消除 code duplication(代码重复)的安全的,被证实过的方法。

Things to Remember

  • copying functions(拷贝函数)应该保证拷贝一个 object(对象)的所有 data members(数据成员)以及所有的 base class parts(基类构件)。
  • 不要试图依据一个 copying functions(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。
TMS320F280039 CMD文件配置避坑指南:从.map文件看懂内存分配与溢出排查 本文详细解析了TMS320F280039芯片的CMD文件配置问题,通过.map文件分析内存分配与溢出排查。文章提供了实用的诊断技巧和解决方案,帮助开发者有效应对段溢出、地址重叠和栈堆冲突等常见问题,优化DSP程序的内存管理。 阅读详情

相关推荐

工业级隔离CAN FD收发器ISO1042设计实战:从原理到EMC防护

在工业自动化与伺服驱动系统中,CAN总线是实现设备间可靠通信的关键技术。其核心原理是通过差分信号传输和仲裁机制,在复杂电磁环境中实现多节点实时数据交换。这项技术的核心价值在于为高噪声、高干扰的工业现场提供了稳定、确定性的通信基础,是构建分布式控制系统不可或缺的环节。其典型应用场景包括新能源汽车电池管理、工业机器人伺服控制以及光伏逆变器集群通信等。本文将聚焦于如何通过高性能隔离CAN FD收发器(如TI的ISO1042)来应对地电位差、浪涌和高压串扰等现场挑战,深入解析其集成的电气隔离、±70V总线故障保护和

weixin_26834815的博客 391

C++ Primer Plus书之--C++ 内联函数和引用变量以及引用作为函数参数, 临时变量

C++内联函数 参考上图: 常规函数在内存中执行的效果是这样的: 运行程序的时候, 操作系统会将指令载入到计算机内存中, 因此每条指令都有特定的内存地址. 当执行到函数的时候, 程序会从当前位置跳到函数的地址, 并在函数指向结束后, 重新返回到当前位置(就像我们阅读文章时遇到脚注, 会跳到脚注看完后, 返回到脚注继续往后看一样). C++内联函数提供了另一种选择, 内联函数的编译代码与其他...

c1392851600的专栏 637

DSP28335新手避坑指南:手把手教你用CCS6.2生成10KHz SPWM(附完整工程)

本文详细介绍了如何使用DSP28335开发板和CCS6.2环境生成10KHz SPWM波形的完整工程指南。从开发环境配置、ePWM模块设置到SPWM算法实现,特别针对新手常见问题提供了解决方案和调试技巧,帮助工程师快速掌握DSP28335的SPWM生成技术。

weixin_42522374的博客 182

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 254

Cadence SIP Layout 简单教程-第二章

[从whp1920 网易博客迁移至优快云] 第一章在正式布线之前做了必须做的准备工作,下面进入正题,打开Candence SIP RF Layout GXL软件。 第一节 导入外形尺寸 打开SIP设置文件保存路径,如下图所示进入导入DXF页面,选中前一章时画好的外框图。打开导入界面后,再进入DXF In Edit/View Layers界面选择所有层,导入Class选择DRAWING FOR...

whp1920的博客 6940

利用Python搭建用户画像系统

用户画像是当下很多企业都会提及的概念,多数情况下会和大数据以及营销挂钩。本文将对用户画像的相关知识进行进行简单的介绍,并利用Python去实现一个简单的用户画像系统。 1.什么是用户画像 用户画像可以理解成是海量数据的标签,根据用户的目标、行为和观点的差异,将他们区分为不同的类型,然后每种类型中抽取出典型特征,赋予名字、照片、一些人口统计学要素、场景等描述,形成一个人物原型 。 用户画像 =...

黎荣恒的博客 3万+

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 394

Shopee动态参数x-sap-Ri与x-sap-Sec逆向解析实战

现代电商前端反爬普遍采用动态参数校验机制,其核心是将设备指纹、DOM状态与时间上下文耦合生成不可复用的请求凭证。x-sap-Ri本质是渲染上下文唯一标识,依赖真实浏览器环境中的DOM树结构、Canvas像素指纹及毫秒级时间扰动;x-sap-Sec则是基于该标识与服务端同步密钥生成的HMAC-SHA256签名,具备强时效性与密钥轮换特性。这类机制显著提升了自动化采集门槛,广泛应用于Shopee商品详情页等高价值数据接口。本文聚焦于从Chrome DevTools调试出发,系统还原参数生成逻辑,覆盖DOM一致性

weixin_30603633的博客 898

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 349

如何修复Windows卡在“正在准备Windows”的问题?这里有详细步骤

如何修复Windows卡在“正在准备Windows”的问题?这里有详细步骤。

wyxtx的博客 5665

autoware 激光-相机联合标定

autoware进行激光-相机联合标定 ~~~ 刚开始进行激光-相机联合标定的时候,出现了不少问题,在此记录一下整体标定流程,以防忘记。在此,特别感谢佳明师弟,在录制数据集时提供的帮助。 ~~~ 0.系统配置: Ubuntu18.04系统 ROS Melodic opencv 3.2.0 vlp-16 一.安装autoware 这部分网上教程挺多的,但我在编译的过程中仍存在不少bug,唉~_~。 二.安装calibration_camera_lidar,从a...

nuo112的博客 1万+

跟我学c++中级篇—类型定义的方式

本文系统阐述了编程中类型定义的重要性及c++中的实现方式。重点对比了宏定义、typedef与modern using三种机制,指出using在可读性、灵活性和模板支持上的优势,推荐优先使用。同时提醒开发者注意别名滥用、类型歧义、错误信息冗长等问题,强调统一规范与合理设计。最终结论:在现代c++开发中,应以using为主,兼顾简洁性与可维护性,避免陷入命名混乱与编译困境。

fpcc的专栏 88

深度学习驱动的股票波动率预测:LSTM和Transformer模型的实战解析

股票市场的波动率一直是金融领域关注的焦点之一。波动率预测对于风险管理、期权定价和资产配置至关重要。深度学习模型,如长短时记忆网络(LSTM)和Transformer,已经在时间序列数据分析领域取得了显著的成就。本博客将向您展示如何使用这两种深度学习模型来进行股票波动率预测,提供清晰的思路和相应的Python代码,以帮助您理解和应用这一重要任务。

斌擎科技 974

【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基

本文系统梳理C++类与对象的核心知识点,涵盖类的定义、访问限定符(public/private)、类作用域、对象实例化及内存布局。重点解析封装思想、this指针机制、空类大小为1字节的原因,以及类与对象在内存中的存储差异。通过对比C语言结构体与C++类,揭示面向对象设计的优势:数据保护、接口统一、命名隔离与代码可维护性提升。内容由浅入深,适合作为学习笔记与复习参考。

2602_95684935的博客 496

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 307

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 450

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

#SQLite 数据库笔记一:(概念 / 终端指令 / SQL 语句 / C 编程 / 回调函数)

数据库 = 数据的仓库,假设数据是海量。能力说明存储数据持久化保存,程序退出数据还在统计数据、分组、排序数据管理增、删、改、查(CRUD)嵌入式设备上也会用数据库(单片机网关、路由器、工业 HMI 都常见),因为 sqlite 体积小、无需服务进程、单文件即可。数据库 = 库 > 表 > 记录(行) > 字段(列)sqlite3 = 开源、C 写的、单文件、免安装、最多 2T 的嵌入式关系型数据库。

lwq20051110的博客 251

1-10 PostgreSQL中多表连接全解:inner/left/right/full/cross join

本文基于 PostgreSQL 15+ 完整拆解 5 种主流连接:内连接、左连接、右连接、全连接、笛卡尔连接。结合可落地的测试数据,演示每种连接的执行逻辑、返回结果,重点剖析 NULL 值对 JOIN 匹配的核心影响,彻底解决多表连接数据丢失、匹配异常、结果错乱的问题。

zhangjw34的专栏 105

【C++ 基础入门】C++继承(下):问题、底层原理与组合

C++继承(菱形继承的问题、虚继承原理、继承与组合、构造顺序)

2601_96587588的博客 802

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级 备考:莫队算法离线区间查询详解

备赛进行时:第十八届蓝桥杯软件赛报名已于 2026-10-08 10:00 开启、12-18 14:00 截止,省赛定在 2027-04-10(C/C++·Python 程序设计 9:00–13:00);中国电子学会 2026 年 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名 9/23–12/2 16:00、统考 12-12;全国青少年信息素养大赛 2026—2027 赛季算法应用主题赛也已进入复盘练兵窗口。

IT_Scratch的博客 170

c++ explicit关键字详解

explicit。

拒绝AI文,努力在AI时代写出感动人心的好文章 151

825 mW 顶上 212 °C:ASM491S 的 105 °C/W、250 mA 短路电流与热关断

25 °C 环境里结温 34.1 °C,125 °C 环境里结温 134.1 °C——后者已经离 150 °C 只有 16 °C 了,但还在范围内。换个角度看同一件事:从 25 °C 和从 125 °C 起算,温升都是 86.6 °C,但一个到 111.6 °C(还在范围内),一个到 211.6 °C(远远超限)。这个算例里 20 °C/W 是我假设的,实际值取决于机壳材料、密封程度、有没有风扇,跨度可以从 5 °C/W 到 50 °C/W 以上。外面 50 °C 的机箱,盒内就是 63.9 °C。

ANSILIC的博客 393

《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试

本文系统完成了Gemini模型通过OpenAI兼容API的接入开发,基于Apifox验证接口规范,设计并实现GeminiProvider类,涵盖初始化、状态检测、全量响应等核心功能。重点处理了代理配置强制校验、Bearer鉴权、JSON序列化与响应解析等工程细节,并通过GTest与CMake完成单元测试,验证链路可用性。项目遵循策略模式,保持与DeepSeek、ChatGPT等模型的统一抽象接口,显著提升SDK可扩展性。

2501_91275995的博客 398

【容斥原理计数】题解:P9489 ZHY的表示法_容斥原理_计数问题_前缀和问题转化_算法竞赛C++

给定 $ n $ 个正整数,求区间 $[l,r]$ 中有多少正整数 $ x $ 可表示为 $ \sum \lfloor \frac{y}{x_i} \rfloor $。通过二分确定最大 $ y $,转化为求 $ 1 \sim H $ 中是某些 $ x_i $ 倍数的数的个数,利用容斥原理与 DFS 枚举子集计算,结合 LCM 预判剪枝及去重优化,时间复杂度 $ O(2^n \log V) $,可高效处理 $ r \le 10^9 $ 的数据。

YLCHUP的博客 106

头歌 数据结构——C&C++线性表实训

本系列任务围绕线性表的链式实现,依次完成顺序构建、逆序构建、排序插入、查找、删除、求长度及栈、队列、集合等应用。通过动态内存分配与指针操作,掌握单链表的基本操作与实际应用,涵盖插入、删除、遍历、搜索等核心功能,最终实现数据结构在典型场景中的灵活运用。

一名敲代码的苦13程序员 244

字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)

本题核心思想是:最少移动步数 = 总长度 - 可保留的最长连续子串长度。未移动字母在T中必须为连续子串,在S中需保持相对顺序(即子序列)。通过枚举T的所有连续子串起点,贪心匹配S中的对应字符,求出最长可保留长度L,答案即为n−L。时间复杂度O(n²),空间复杂度O(n),适用于n≤1000。

冻柠茶飞冰走茶 592
上一篇: [翻译] Effective C++, 3rd Edition, Item 11: 在 operator= 中处理 assignment to self(自赋值)
下一篇: [翻译] Effective C++, 3rd Edition, Item 13: 使用 objects(对象)管理资源
fatalerror99
博客等级 码龄24年 166粉丝 20原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值