构造函数语义学——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 ...
随机推荐
- ASP.NET Core SignalR:集线器Hubs
一.什么是集线器hubs 通过SignalR的集线器hubs中定义的方法,服务器可以调用连接中的客户端定义的方法,而客户端也可以调用服务器端集线器中定义的方法.SignalR负责实现了客户端和服务器之 ...
- C++输入输出常用格式(cin,cout,stringstream)
输入格式 1.cin>>a; 最基本的格式,适用于各种类型.会过滤掉不可见字符例如空格,TAB,回车等 2.cin>>noskipws>>ch[i]; 使用了 no ...
- Linux 笔记 - 第十六章 LNMP 之(一) 环境搭建
博客地址:http://www.moonxy.com 一.前言 LNMP 中的 N 指 Nginx,在静态页面的处理上,Nginx 较 Apache 更胜一筹:但在动态页面的处理上,Nginx 并不比 ...
- 02.Django基础二之URL路由系统
一 URL配置 Django 1.11版本 URLConf官方文档 URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL与要为该URL调用的视图函数之间的映射表.你就是以这 ...
- 05:(H5*) node、npm、nrm
目录: 1:NPM 2:CNPM 3:NRM 4:homebrew 5:具体指令 6: -g -S -D 1:NPM NPM的全称是Node Package Manager, 是一个NodeJS包管理 ...
- JAVA用递归来判断回文串
用递归来判断回文串 思路: ①首先如果是空串,一个字符,则判断是回文串 ②若不满足①,则我们需要对字符串进行递归判断,我们会想到要用第一个字符和最后一个字符进行比较,若相同则,第二个和倒数第二个进行比 ...
- Python语法基础之对象(字符串、列表、字典、元组)
转载自https://blog.csdn.net/lijinlon/article/details/81630154 字符串 String 定义:S = 'spam' 索引:S[0] = 's';S[ ...
- C#使用Oxyplot绘制监控界面
C#中可选的绘图工具有很多,除了Oxyplot还有DynamicDataDisplay(已经改名为InteractiveDataDisplay)等等.不过由于笔者这里存在一些环境上的特殊要求,.Net ...
- 网站开发过程中的URL写法
在开发网页和服务器时发现,在很多地方需要写超链接 那么可以将超链接的使用者分为服务器和浏览器,以区分不同的写法 地址可能使用的情况: 1.跳转 2.转发 3.服务器资源地址 4.浏览器超链接 impo ...
- 提交第一个spark作业到集群运行
写在前面 接触spark有一段时间了,但是一直都没有真正意义上的在集群上面跑自己编写的代码.今天在本地使用scala编写一个简单的WordCount程序.然后,打包提交到集群上面跑一下... 在本地使 ...