构造函数语义学——Copy Constructor 篇
构造函数语义学——Copy Constructor 篇
本文主要介绍《深度探索 C++对象模型》之《构造函数语义学》中的 Copy Constructor
构造函数的调用时机
首先需要明确,构造函数何时会被调用呢?cppreference 中已经有了足够详细地说明:
凡在对象从同类型的另一对象(以直接初始化或复制初始化)初始化时,调用复制构造函数(除非重载决议选择了更好的匹配或其调用被消除),情况包括:
初始化:T a = b; 或 T a(b);,其中 b 类型为 T;
函数实参传递:f(a);,其中 a 类型为 T 而 f 为 Ret f(T t);
函数返回:在如 T f() 这样的函数内部的 return a;,其中 a 类型为 T,它没有移动构造函数。
编译器合成 copy constructor 的条件
在之前《构造函数语义学——Default Constructor 篇》一文中,我们分析了编译器产生 default constructor 的条件,以及编译器所产生的 default constructor 的类型(trivial & non-trivial);对于构造函数来说,其原理也是大致类似的,只是具体的细节条件不同,此文中就不再给出具体的证明,读过前一篇博文的读者也应该能够自己分析,此文只给出具体的条件
编译器隐式声明&定义 copy constructor 的条件
隐式声明的复制构造函数
若不对类类型(struct、class 或 union)提供任何用户定义的复制构造函数,则编译器始终会声明一个复制构造函数,作为其类的非 explicit 的 inline public 成员。
与 default constructor 类似,只要没有任何 user_declared 的 copy constructor,那么编译器就会为我们自动声明一个 copy constructor(这一点与《深度探索 C++对象模型》中所述不同)
隐式定义的复制构造函数
若隐式声明的复制构造函数未被弃置,则当其被 ODR 式使用时,它为编译器所定义(即生成并编译函数体)。对于 union 类型,隐式定义的复制构造函数(如同以 std::memmove)复制其对象表示。对于非联合类类型(class 与 struct),该构造函数用直接初始化,按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制。
trivial copy constructor 的条件
编译器自动合成的 copy constructor 也是分为 trivial 和 non-trivial 的
对于 trivial copy constructor 的条件,cppreference 中也给出了详细的说明:
当下列各项全部为真时,类 T 的复制构造函数为平凡的:
它不是用户提供的(即它是隐式定义或预置的),且若它被预置,则其签名与隐式定义的相同;
T 没有虚成员函数;
T 没有虚基类;
为 T 的每个直接基类选择的复制构造函数都是平凡的;
为 T 的每个类类型(或类类型数组)的非静态成员选择的复制构造函数都是平凡的;
而在《深度探索 C++对象模型》中有一句话“决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的bitwise copy semantics”;即如果一个 class 展现出了 bitwise copy semantics,那么编译器为其合成的 copy constructor 就是 trivial 的
换言之,如果不满足 bitwise copy semantics,那么编译器合成的 copy constructor 就是 non-trivial 的。何时一个 class 不表现出 bitwise copy semantics 呢?书中给了四个条件(略有修改):
- 当 class 内含一个 member object,而后者的 class 中的有一个 copy constructor(而后者 class 的 copy constructor 必须是 non-trivial 的)
- 当 class 继承自一个 base class,而这个 base class 存在一个 copy constructor(该 base class 的 copy constructor 必须是 non-trivial 的)
- class 声明了 virtual function
- class 派生自一个继承链,而该继承链中存在一个或多个 virtual base class
其实这个四个条件相当于 cppreference 中提到的成为 trivial copy constructor 的相反条件
编译器合成的 copy constructor 的行为
trivial copy constructor 的行为
关于 trivial copy constructor 的行为,cppreference 也有提到:
非联合类的平凡复制构造函数,效果为复制实参的每个标量子对象(递归地包含子对象的子对象,以此类推),且不进行其他动作。不过不需要复制填充字节,甚至只要其值相同,每个复制的子对象的对象表示也不必相同。
这句话的意思是说,如果编译器合成的出来 copy constructor 是 trivial 的,它展现出这种行为:逐个字节的拷贝所有内容
举个例子:
class A {
private:
int _a;
};
int main() {
A a;
A aa = a;
return 0;
}
其中 A aa = a;这一句,会调用编译器产生的 trivial copy constructor,该 trivial copy constructor 会一个字节一个字节的把 a 中的成员变量的值拷贝到 aa 对应的成员变量中去
这似乎看起来挺好的呀,也正是我们所需要的结果,但是,如果 class A 中的成员变量是一根指针,那么问题就大了:
#include <iostream>
using namespace std;
class A {
public:
int *p;
};
int main() {
A a;
int val = 1;
a.p = &val;
A aa = a;
cout << a.p << endl;
cout << aa.p << endl;
*(aa.p) = 2;
cout << *(a.p) << endl;
cout << *(aa.p) << endl;
}
// 上述程序的输出为
0x7ffc5d760414
0x7ffc5d760414
2
2
也就是说,在编译器自动为我们合成的 trivial copy constructor 的行为中,复制了 a 的指针给了 aa(浅拷贝),也就是说 a 和 aa 中的指针 p 指向了相同的地址!!!
在这种含有指针的情况下,编译器产生的 trivial copy constructor 的行为便不是我们所希望的,我们必须手动显示的定义一个符合我们需求的 copy constructor 来完成对指针的拷贝
non-trivial copy constructor 的行为
cppreference 中已经说了:
对于非联合类类型(class 与 struct),该构造函数用直接初始化,按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制。
non-trivial copy constructor 一个很重要的行为是:确保 vptr 的准确设定。(因为只要包含虚机制,那么编译器自动合成的 copy constructor 就不可能是 trivial 的)
上面一点,书中已经说的足够清楚,此文不再赘述
总结
- copy constructor 在特定条件下,编译器也会为我们自动合成
- 编译器合成的 copy constructor 也是分为 trivial 和 non-trivial 的
- 要时刻牢记 trivial copy constructor 的条件与行为
- 当成员变量涉及指针时,最好的做法就是显式提供自定义的 copy constructor 来满足需求
构造函数语义学——Copy Constructor 篇的更多相关文章
- 构造函数语义学——Default Constructor篇
构造函数语义学--Default Constructor 篇 这一章原书主要分析了:编译器关于对象构造过程的干涉,即在对象构造这个过程中,编译器到底在背后做了什么 这一章的重点在于 default c ...
- 构造函数语义学——Copy Constructor 的构造操作
前言 在三种情况下,会以一个 object 的内容作为另一个 class object 的初值: object明确初始化 class X{...}; X x; X xx = x; object 被当作 ...
- C++ 类 复制构造函数 The Copy Constructor
一.复制构造函数的定义 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性.复制构造函数创建一个新的对象,作为另一个对象的拷贝.复制构造函数只含有一个形参,而且其形参为本类对象的引用.复制构 ...
- Effective C++ 第0章 copy constructor和copy assignment operator
拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...
- copy constructor和copy assignment operator的区别
拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...
- 构造函数语义学之Copy Constructor构建操作(2)
二.详述条件 3 和 4 那么好,我又要问大家了,条件1 和 2比较容易理解.因为member object或 base class 含有copy constructor.那么member objec ...
- 构造函数语义学之Copy Constructor构建操作(1)
一.Copy Constructor的构建操作 就像 default constructor 一样,如果class没有申明一个 copy constructor,就会隐含的声明或隐含的定义一个.生成的 ...
- 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作
C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...
- (C++)关于拷贝构造函数 Copy Constructor
题目: In which of the following scenarios is a Copy Constructor called or invoked? A. When no conve ...
随机推荐
- Winform中自定义xml配置文件后对节点进行读取与写入
场景 Winform中自定义xml配置文件,并配置获取文件路径: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100522648 ...
- java使用FileSystem上传文件到hadoop分布式文件系统配置
Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://sparkclust ...
- 关于解决web编码问题的总结
网页的编码问题,一般分为两个方面 1 是网页本身的编码格式, 一般不同的操作系统网页文件存取的编码是不一样的, 但一般来说, 新建网页文件一般都和IDE有关,因为我们平时我是使用编辑工具新建网页文件. ...
- sql注入(从入门到进阶)
随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多.但是由于这个行业的入门门槛不高,程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码的时候,没有对用户输入数据的合法性进 ...
- Vue路由(vue-router)详细讲解指南
中文文档:https://router.vuejs.org/zh/ Vue Router 是 Vue.js 官方的路由管理器.它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌.路由实际 ...
- java8 新特性精心整理
前言 越来越多的项目已经使用 Java 8 了,毫无疑问,Java 8 是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和 JVM 等方面的十多个新特 ...
- GDB 调试指南
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 00 介绍 ...
- maven的settings.xml详解
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理二 (二十)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...
- Python学习笔记整理总结【Django】:Model操作(二)
1.操作汇总 # 增 # # models.Tb1.objects.create(c1='xx', c2='oo') 增加一条数据,可以接受字典类型数据 **kwargs # obj = models ...