C++拷贝构造函数的合成

默认构造函数和拷贝构造函数在必要的时候才由编译期合成出来


文章目录

  • 拷贝构造函数
  • 编译器合成拷贝构造函数的四种情况
    • 情况一 一个类有一个带有拷贝构造函数的类对象成员变量
    • 情况二 派生类的基类有一个拷贝构造函数
    • 类声明了一个或多个虚函数
    • 情况四 类派生自一个继承串联且有一个或多个虚基类
  • 参考资料


拷贝构造函数

拷贝构造函数,通俗地理解是以一个对象的内容作为另一个对象的初值。
有三种情况,会以另一个对象的内容作另一个对象的初值:

  1. 对一个对象明确的初始化操作
class X {...};
X x;// 1
X xx = x;// 2
X xx(x);
  1. 当对象作为参数传进某个函数
extern void foo( X x );void bar()
{X xx;foo( xx );
}
  1. 当函数传回一个类对象
X foo_bar()
{X xx;// ...return xx;
}

下面的程序代码结合了上面三种情况

#include <iostream>
using namespace std;class X
{
public:X() = default;X(const X& x){m_x = x.m_x;cout << "copy constructor" << endl;}int m_x;
};X foo(X x)
{x.m_x = 5;cout << "foo" << endl;return x;
}int main()
{X x{};X xx(x);foo(xx);
}

输出如下,可以看到调用三次拷贝构造函数

通过VS2022下的汇编码也可以看到调用了三次拷贝构造函数,main 函数中对应前两种情况,foo 函数中对应最后一种情况


编译器合成拷贝构造函数的四种情况

上面的例子中提供了显式的拷贝构造函数,如果类没有提供显式的拷贝构造函数,编译器会作何操作,考虑下面这个例子。

我们有一个类 String,其只包含基础元素

class String{
public:
// ... 没有显式的拷贝构造函数
private:char* str;int len;
};

那么编译器实际不会合成出一个拷贝构造函数,而是进行逐元素初始化

其完成方式就好像逐个设定每一个成员一样

// 语意相等
verb.str = noun.str;
verb.len = noun.lenl

如果 String 对象是另一个类的成员,比如下面的 Word

class Word {
public:Word(String& word, int occurs) : _word(word), _occurs(occurs) {}
private:int _occurs;String _word;
};

那么先拷贝Word 的内置基础变量 _occurs,然后再拷贝 _word 中的基础变量,[word] 位置对应 _occurs 的地址,后面的 [ebp-30h],[ebp-44h]分别对应word1word2 中的 String 对象成员的地址,可以通过下面的地址推出。


当然如果 _word_occurs 调换顺序,那么初始化的顺序也会调换过来


情况一 一个类有一个带有拷贝构造函数的类对象成员变量

当类中含一个成员对象,这个成员对象对应的类声明有一个拷贝构造函数时(无论是类设计者明确地声明,或是被编译器合成)。
比如下面有一个类 Word 其有一个成员对象

class Word {
public:Word( const String& s, int c) : str( s ), cnt( c ) {}
private:int cnt;String str;
};

成员对象 str 对应的类 String 有一个定义好的拷贝构造函数

class String {
public:String(const char* s){str = new char[strlen(s) + 1];strcpy_s(str, strlen(s) + 1, s);}String(const String& s) : str(s.str) { std::cout << "调用String的拷贝构造函数" << std::endl; }
private:char* str;
};

那么为了调用 String 下的拷贝构造函数,编译器会为 Word 合成一个拷贝构造函数,类似如下代码:

inline Word::Word( const Word& wd )
{	str.String::String( wd.str );cnt = wd.cnt;
}

汇编代码中也可以看到调用了 Word 的拷贝构造函数


情况二 派生类的基类有一个拷贝构造函数

当类继承自一个基类,该基类存在一个拷贝构造函数(也是无论是明确声明或是被编译器合成得到的),此时编译器都会为该派生类合成一个拷贝构造函数,以调用基类的拷贝构造函数。

比如一个类 WordSuper 派生自上面的 Word

class WordSuper : public Word {
public:WordSuper( const String& s, int c ) : Word( s, c ) {}
};

我们知道 Word 类有一个编译合成的拷贝构造函数,那么编译器也会为 WordSuper 合成一个拷贝构造函数

进入到该拷贝构造函数内部,可以看到其调用了 Word 的拷贝构造函数


类声明了一个或多个虚函数

之前谈到虚函数表,要在对象创建时初始化好其虚函数表指针 vptr,那么编译器对于每一个构造函数都应该在代码前插入对虚函数表指针 vptr 的初始化操作,那么如果此时没有拷贝构造函数,编译器应该要合成一个拷贝构造函数。

比如下面的类 X 有一个虚函数 f() ,且没有显式的拷贝构造函数

class X
{virtual void f() {std::cout << "X::f()" << std::endl;}
};

那么编译器会为其生成一个拷贝构造函数

也可以看到编译器合成的拷贝构造函数中对虚函数指针进行了初始化,使其指向合适的虚函数表地址

当然对于上面的 case,逐位拷贝也是可行的,但是考虑到如果此时有一个类 Y 继承自 X 并重写了虚函数 f(),那么其虚函数指针指向不同的虚函数表(当然不重写也是指向不同的虚函数表),那么此时我们用 Y y; X x = y;,如果用逐位拷贝,那么会导致 x 的vptr指向类 Y 的虚函数表,那么是不正确的。


情况四 类派生自一个继承串联且有一个或多个虚基类

这种也和初始化虚函数表指针 vptr 类似,有虚基类,对象在创建时,需要初始化 vbtr,使得其指向合适的虚基类表地址,虚基类表中包含每个虚基类,在该类中的地址偏移。

考虑下面这个 case

#include <iostream>class ZooAnimal {
public:int m_x;
};class Raccoon : public virtual ZooAnimal
{
public:int m_y;
};class RedPanda : public Raccoon
{
public:int m_z;
};
int main()
{Raccoon rocky{};Raccoon little_critter = rocky;RedPanda little_red;Raccoon little_critter2 = little_red;
}

使用逐位拷贝来用 Raccoon 对象来初始化 Raccoon 对象是够用的,但是如果用其派生类 RedPanda 来初始化,那么逐位拷贝会出问题,会将 RedPanda 对应的 vbtable 的地址错误地初始化给 Raccoon 类型的对象,所以此时必须要有一个编译器合成的拷贝构造函数来完成正确的初始化 vbptr。

可以看到在编译器合成的拷贝构造函数中,初始化了 vbptr。


参考资料

《深度探索C++对象模型》—— Stanley B.Lippman著,侯捷译

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

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

相关文章

JUC-并发编程19-定时任务定时线程池-ScheduledThreadPoolExecutor

1、结构图 2、初识 ScheduledThreadPoolExecutor用来处理延时任务或定时任务。 流程如下&#xff1a; 2.1 定时任务分为四种 如下&#xff1a; 未来执行一次的任务&#xff0c;无返回值&#xff1b; 未来执行一次的任务&#xff0c;有返回值&#xff1b; 未来按固定频率重复…

安川YASKAWA机器人FS100控制箱维修全攻略

本文将一起探讨安川机器人控制箱维修和YASKAWA机械手FS100控制柜故障&#xff0c;从故障诊断到维修技巧。注意&#xff0c;在安川机械臂控制器FS100维修过程中&#xff0c;遇到复杂的问题&#xff0c;不要犹豫&#xff0c;及时联系子锐机器人&#xff0c;让您的机器人重获新生&…

chrome 安装devtools

chrome 安装devtools 下载安装 链接&#xff1a;https://github.com/vuejs/devtools 选择对应版本&#xff1a; 安装yarn 下载 npm install -g yarn --registryhttps://registry.npmmirror.com进入下载的目录安装依赖 yarn install --registryhttps://registry.npmmirror.…

一篇了解reactor框架特性

一篇了解reactor框架特性 本文档的一些典型的名词如下&#xff1a; Publisher&#xff08;发布者&#xff09;、Subscriber&#xff08;订阅者&#xff09;、Subscription&#xff08;订阅 n.&#xff09;、subscribe&#xff08;订阅 v.&#xff09;。event/signal&#xff0…

抖音 通用交易系统 下单 密钥生成

已PHP为例 前提提条件 必须在 linux 系统中 生成 准备工作 在小程序中 生成应用公匙 把生成的公匙 复制 在linux 系统中 创建文件 private_key.pem 并将公匙粘贴 接下来打开命令 执行命令即可 openssl genrsa -out private_key.pem 2048 rsa -in private_key.pem -pubo…

分治策略 --- 快排归并

目录 分治-快排 一、颜色分类 二、排序数组 三、数组中的第K个最大元素 四、库存管理 分治-归并 一、排序数组 二、交易逆序对的总数 三、计算右侧小于当前元素的个数 四、翻转对 分治是一种思想&#xff0c;也就是将大问题分解成小问题&#xff0c;一直分到小问题可…

【Camera KMD ISP SubSystem笔记】CAM SYNC与DRQ②

DRQ的作用&#xff1a; DRQ负责调度管理pipeline里的node处理逻辑(通过node之间的dependency依赖机制) 利用多线程并行处理Pipeline中并行的node&#xff0c;加快处理速度 DRQ运转流程&#xff1a; DRQ先告诉node fill dependency&#xff0c; 此时seq id 为0…

如何优雅的实现 iframe 多层级嵌套通讯

前言 在前端开发项目中&#xff0c;不可避免的总会和 iframe 进行打交道&#xff0c;我们通常会使用 postMessage 实现消息通讯。 如果存在下面情况&#xff1a; iframe 父子通讯iframe 同层级通讯iframe 嵌套层级通讯 当面对这种复杂的情况的时候&#xff0c;通讯不可避免…

Unity 物体触碰事件监听

声明委托 public delegate void MyDelegate(Collider trigger); C# 委托&#xff08;Delegate&#xff09; | 菜鸟教程 (runoob.com)https://www.runoob.com/csharp/csharp-delegate.html 定义委托 public MyDelegate onTriggerEnter; public MyDelegateonTriggerStay; pub…

用来传输文件的协议-FTP

一.FTP协议--文件传输协议 1.了解FTP协议 &#xff08;1&#xff09;FTP服务是用来传输文件的协议 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是TCP/IP协议组中的协议之一&#xff0c;用于互联网上的控制文件的双向传输。是传输文件到Linu…

《Fundamentals of Power Electronics》——全桥型隔离降压转换器

以下是关于全桥型隔离降压转换器的相关知识点&#xff1a; 全桥变压器隔离型降压转换器如下图所示。 上图展示了一个具有二次侧绕组中心抽头的版本&#xff0c;该电路常用于产生低输出电压。二次侧绕组的上下两个绕组可以看作是两个单独的绕组&#xff0c;因此可以看成是具有变…

Internal server error: [less] Unrecognised input

我之前查了资料&#xff0c;网上有的人说是 less 的配置不正确&#xff08;这种问题引起的可以查找其他博客看&#xff09;&#xff0c;但是后面经过我慢慢的查找&#xff0c;还有一种可能&#xff0c;就是 less 的写法不对&#xff0c;下面我来解释一下我的错误和处理过程 在…

kaggle无法注册怎么办

在浏览kaggle网站&#xff0c;或者是参加kaggle竞赛时&#xff0c;常常会遇到需要登陆kaggle账号的情况。而在注册时&#xff0c;却发现无论如何也无法弹出人机识别的验证码&#xff0c;导致无法注册成功。本文会手把手的讲解一种注册kaggle的方法&#xff08;edge浏览器&#…

安装依赖报错前端安装某个依赖安装不上可能是node版本过高 升级或者降低node版本方式

安装依赖报错安装某个依赖安装不上可能是node版本过高 升级或者降低node版本方式 安装某个依赖安装不上 或者node版本过高 升级或者降低node版本 收藏关注一下吧 开发中难免总会需要切换node版本 需要的时候在找麻烦 主页 中还有更多干货分享

分享开放原子AtomGit开源协作平台评测报告

AtomGit平台的总体介绍 开放原子开源基金会是致力于推动全球开源事业发展的非营利机构&#xff0c;于 2020 年 6 月在北京成立&#xff0c;由阿里巴巴、百度、华为、浪潮、360、腾讯、招商银行等多家龙头科技企业联合发起。目前有三个主要机构设置&#xff0c;技术监督委员会&…

You need know something from Xcode 9

xcode 9 一些快捷的功能使用介绍 首先是弹框 目前发现弹框中的提取方法等功能存在一些问题&#xff0c;期待后续的版本能解决这些问题&#xff0c;弹框里面主要包含跳转变量或者方法的跳转、快捷帮助、折叠方法、重命名方法名称、提取方法等功能 笔者觉得rename功能比较好用&…

【新手入门】Git的使用方法,上传自己的项目到GitHub上

Git新手教程 一、Git下载安装二、初始化设置1.网端设置2.用户设置 三、开始上传自己项目1.创建新文件夹&#xff0c;克隆项目地址2.上传文件3.成功运行并上传的界面 报错1.fatal: unable to access https://github.com/ssrzero123/STF-YOLO.git/: error setting certificate fi…

NDK 编译(二)—— NDK 编译与集成 FFmpeg

NDK 编译系列文章共三篇&#xff0c;目录如下&#xff1a; NDK 编译&#xff08;一&#xff09;—— Linux 知识汇总 NDK 编译&#xff08;二&#xff09;—— NDK 编译与集成 FFmpeg NDK 编译&#xff08;三&#xff09;—— CMake 原生构建工具 在使用 NDK 进行音视频开发时&…

icloud里面的通讯录怎么全部导出,通讯录格式如何转换,简单!

随着科技的发展&#xff0c;我们的日常生活越来越离不开手机和各种应用程序。通讯录作为手机中最重要的功能之一&#xff0c;记录着我们的亲朋好友、同事和业务伙伴的联系方式。因此&#xff0c;定期备份通讯录变得尤为重要。iCloud作为苹果公司提供的一项云服务&#xff0c;可…

【触摸案例-控件不能响应的情况 Objective-C语言】

一、接下来,我们来说这个“控件不能响应的情况”, 1.素材里边,有一个“不接受用户交互的情况”,这么一个代码,把它打开, 把这个项目啊,复制过来,改一个名字,叫做“04-控件不能响应的情况”, 打开之后,command + R,运行一下, 在storyboard上,你也可以看得出来,我…