构造函数语义学——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 呢?书中给了四个条件(略有修改):

  1. 当 class 内含一个 member object,而后者的 class 中的有一个 copy constructor(而后者 class 的 copy constructor 必须是 non-trivial 的)
  2. 当 class 继承自一个 base class,而这个 base class 存在一个 copy constructor(该 base class 的 copy constructor 必须是 non-trivial 的)
  3. class 声明了 virtual function
  4. 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 的)

上面一点,书中已经说的足够清楚,此文不再赘述

总结

  1. copy constructor 在特定条件下,编译器也会为我们自动合成
  2. 编译器合成的 copy constructor 也是分为 trivial 和 non-trivial 的
  3. 要时刻牢记 trivial copy constructor 的条件与行为
  4. 当成员变量涉及指针时,最好的做法就是显式提供自定义的 copy constructor 来满足需求

构造函数语义学——Copy Constructor 篇的更多相关文章

  1. 构造函数语义学——Default Constructor篇

    构造函数语义学--Default Constructor 篇 这一章原书主要分析了:编译器关于对象构造过程的干涉,即在对象构造这个过程中,编译器到底在背后做了什么 这一章的重点在于 default c ...

  2. 构造函数语义学——Copy Constructor 的构造操作

    前言 在三种情况下,会以一个 object 的内容作为另一个 class object 的初值: object明确初始化 class X{...}; X x; X xx = x; object 被当作 ...

  3. C++ 类 复制构造函数 The Copy Constructor

    一.复制构造函数的定义 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性.复制构造函数创建一个新的对象,作为另一个对象的拷贝.复制构造函数只含有一个形参,而且其形参为本类对象的引用.复制构 ...

  4. Effective C++ 第0章 copy constructor和copy assignment operator

    拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...

  5. copy constructor和copy assignment operator的区别

    拷贝构造函数(copy constructor)被用来以一个对象来初始化同类型的另一个对象,拷贝赋值运算符(copy assignment operator)被用来将一个对象中的值拷贝到同类型的另一个 ...

  6. 构造函数语义学之Copy Constructor构建操作(2)

    二.详述条件 3 和 4 那么好,我又要问大家了,条件1 和 2比较容易理解.因为member object或 base class 含有copy constructor.那么member objec ...

  7. 构造函数语义学之Copy Constructor构建操作(1)

    一.Copy Constructor的构建操作 就像 default constructor 一样,如果class没有申明一个 copy constructor,就会隐含的声明或隐含的定义一个.生成的 ...

  8. 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作

    C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...

  9. (C++)关于拷贝构造函数 Copy Constructor

    题目: In which of the following scenarios is a Copy Constructor called or invoked? A.    When no conve ...

随机推荐

  1. Winform中实现ZedGraph曲线图的图像复制到剪切板、打印预览、获取图片并保存、另存为的功能

    场景 Winforn中设置ZedGraph曲线图的属性.坐标轴属性.刻度属性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10 ...

  2. Linux 笔记 - 第九章 Linux 中软件的安装

    博客地址:http://www.moonxy.com 一.前言 在 Linux 系统中,应用程序的软件包主要分为两种:1)第一种是二进制的可执行软件包,也就是解开包后就可以直接运行.在 Windows ...

  3. 亮剑.NET学习札记

    学习前提要: 因为书的版本过老,有些章节不学了,要学的包括以下章节 暂定:1,2,4,5,6,7,9,10,11,12,13,14,15,16,17,18,附录A 第一章:主要是介绍.NET,包括面向 ...

  4. NPOI 导出添加批注功能

    这个问题在网上搜,都是说如下即可: //添加批注HSSFPatriarch patr = (HSSFPatriarch)sheet.CreateDrawingPatriarch();HSSFComme ...

  5. Android进程的优先级说明

    引言 Android系统尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除一些进程来回收内存.为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进 ...

  6. Kylin配置Spark并构建Cube

    HDP版本:2.6.4.0 Kylin版本:2.5.1 机器:三台 CentOS-7,8G 内存 Kylin 的计算引擎除了 MapReduce ,还有速度更快的 Spark ,本文就以 Kylin ...

  7. 生物医学命名实体识别(BioNER)研究进展

    生物医学命名实体识别(BioNER)研究进展 最近把之前整理的一些生物医学命名实体识别(Biomedical Named Entity Recognition, BioNER)相关的论文做了一个Bio ...

  8. Hive之行转列与列转行

    行转列 原始数据: 需求: 把星座和血型一样的人归类到一起.结果如下: 射手座,A 大海|凤姐 白羊座,A 孙悟空|猪八戒 白羊座,B 宋宋 实现: vi person_info.txt 孙悟空 白羊 ...

  9. API离线查看工具【包括!!所用常用!!开发语言的API】

    我相信对于每一个开发人员来说,都是不喜欢死记硬背API的,一些常用的方法或者接口我们是可以直接信手捏来的,或者说直接使用开发工具的自动代码提示,也能很快的找到自己想用的API,如果是自己从未使用过的, ...

  10. 初次接触python时,整理的一些基础操作

    1.window下python简单使用 (1).使用工具网址 https://jingyan.baidu.com/article/9f7e7ec0ec2e676f2915545f.html (2).各 ...