【C++对象模型】构造函数语意学之二 拷贝构造函数
关于默认拷贝构造函数,有一点和默认构造函数类似,就是编译器只有在【需要的时候】才去合成默认的拷贝构造函数。
在什么时候才是【需要的时候】呢?
也就是类不展现【bitwise copy semantics】时,即不展现【逐位次拷贝】时,才会合成默认拷贝构造函数。
所谓的【逐位次拷贝】,也就是简单的赋值,不管类内的数据成员是int还是char*指针,都是简单的赋值,这叫【逐位次拷贝】。
那什么请下不展现【逐位次拷贝】呢?
有四种情况:
①类中有一个类对象成员,而该类对象成员声明了一个默认拷贝构造函数(不管这个默认拷贝构造函数是显式声明的还是编译器合成的)
②类继承自一个基类,而该基类有一个默认拷贝构造函数(不管这个默认拷贝构造函数是显式声明的还是编译器合成的)
③类声明了一个或多个虚函数时
④类派生自一个继承链,其中有一个或多个虚基类时
------------------------------
时隔几日,我又回来重新要修改一下这篇博客,原谅我这一生不羁放纵爱自由,今天在群里看到了一个哥们问问题,大概是问
CObj obj1 = obj2; 是否是调用拷贝构造函数而不是调用重载的operator=,我回答了一下:当有拷贝构造函数的时候是不会调用operator=()的; 我的言外之意是:如果程序员没有手动定义拷贝构造或者编译器没有为该类合成默认的拷贝构造(没有上面4种情况),则就会去调用operator=运算符(虽然我明知道上面语句是定义,是从无到有的过程),其过程是首先调用CObj类的默认构造函数初始化obj1,然后再调用operator=()为其赋值。嗯,我是这么想的,而且也觉得这也应该是对的,于是刚才回来试了一下代码。
原来的类定义如下:
class CTest
{
public:
CTest(int a, int b);
virtual ~CTest();
CTest(const CTest& obj);
CTest& operator=(const CTest& obj); protected:
int m_nValue1;
int m_nValue2;
}; CTest::CTest(int a, int b) : m_nValue1(a), m_nValue2(b){cout << "构造函数被调用\r\n";}
CTest::~CTest(){}
CTest::CTest(const CTest& obj)
{
cout << "拷贝构造函数被调用\r\n";
this->m_nValue1 = obj.m_nValue1;
this->m_nValue2 = obj.m_nValue2;
} CTest& CTest::operator=(const CTest& obj)
{
cout << "重载赋值运算符\r\n";
this->m_nValue1 = obj.m_nValue1;
this->m_nValue2 = obj.m_nValue2; return *this;
}
可以看到,为该类定义了一个virtual析构函数,一个默认拷贝构造函数,按照这样的类定义,有如下使用代码
CTest get(CTest obj)
{
CTest obj2 = obj; return obj2;
}
int _tmain(int argc, _TCHAR* argv[])
{
CTest obj(, ); CTest obj2 = get(obj); return ;
}
有一个get函数,该函数使用一个CTest类对象作为参数,返回一个CTest类对象,在main函数中,首先定义了一个obj对象,然后使用obj作为参数调用get函数,将返回值赋值给obj2,

其执行情况如上图:
①构造函数被调用一次, CTest obj(10, 20);
②拷贝构造函数第一次,obj作为实参向get函数传参时
③拷贝构造函数第二次,get函数内部CTest obj2 = obj;
④拷贝构造函数第三次,返回之前,在get函数内部,使用get函数内部的obj2作为参数,调用main函数里的obj2的拷贝构造函数。
第四种情况这种情况是经过编译器优化后的。
上面的执行情况符合预期,下面我们对该类的定义进行一下修改
去掉默认拷贝构造函数,同时要将析构函数改为非virtual的,因为如果不改,则编译器会为该类合成一个默认的拷贝构造函数(【不展现逐位拷贝】的第③中情况);
class CTest
{
public:
CTest(int a, int b);
//virtual ~CTest();
~CTest();
CTest& operator=(const CTest& obj); protected:
int m_nValue1;
int m_nValue2;
}; CTest::CTest(int a, int b) : m_nValue1(a), m_nValue2(b){cout << "构造函数被调用\r\n";}
CTest::~CTest(){} CTest& CTest::operator=(const CTest& obj)
{
cout << "重载赋值运算符\r\n";
this->m_nValue1 = obj.m_nValue1;
this->m_nValue2 = obj.m_nValue2; return *this;
}
使用代码不变
CTest get(CTest obj)
{
CTest obj2 = obj; return obj2;
}
int _tmain(int argc, _TCHAR* argv[])
{
CTest obj(, ); CTest obj2 = get(obj); return ;
}
执行情况如下

可以看到执行结果:只调用了一次构造函数,这次调用跟前面的一样,都是main函数中定义obj时调用的,现在单步调试一下过程中的变量值


现在,我们再次修改一下代码,仅仅是把类的析构函数改为virtual的,仍然没有手动定义拷贝构造函数


可以看到,形参和实参的虚函数表指针的值是相同的,C++对象模型里说到过(《深度探索C++对象模型》p55),这种情况是bitwise copy,此时是安全的。
【C++对象模型】构造函数语意学之二 拷贝构造函数的更多相关文章
- C++中:默认构造函数、析构函数、拷贝构造函数和赋值函数——转
对于一个空类,编译器默认产生4个成员函数:默认构造函数.析构函数.拷贝构造函数和赋值函数.1.构造函数:构造函数是一种特殊的类成员,是当创建一个类的时候,它被调用来对类的数据成员进行初始化和分配内存. ...
- C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)
1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...
- C++ //构造函数调用规则 //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝函数(值拷贝) //2.如果我们写了有参构造函数 编译器就不会提供默认构造函数 但是会提供拷贝构造函数 //3.如果我们写了拷贝函数 编译器就不再提供 默认 有参 构造函数
//构造函数调用规则 #include <iostream> using namespace std; //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) ...
- String类的构造函数,析构函数、拷贝构造函数和赋值函数
(1)构造函数 String::String(const char *str) { if(str==NULL) { m_data = new char[1]; *m_data = ‘\0’; } el ...
- C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)
[1]为什么空类可以创建对象呢? 示例代码如下: #include <iostream> using namespace std; class Empty { }; void main() ...
- 《深度探索C++对象模型》第二章 | 构造函数语意学
默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...
- C++拷贝构造函数总结
C++拷贝构造函数总结 目录: 拷贝构造函数的基础知识 拷贝构造函数的使用 拷贝构造函数的行为 1.拷贝构造函数的基础知识 拷贝构造函数(copy constructor)是构造函数,是拷贝已经存在的 ...
- 【转】C++的拷贝构造函数深度解读,值得一看
建议看原帖 地址:http://blog.csdn.net/lwbeyond/article/details/6202256 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很 ...
- [Reprint]C++友元函数与拷贝构造函数详解
这篇文章主要介绍了C++友元函数与拷贝构造函数,需要的朋友可以参考下 一.友元函数 1.友元函数概述: (1)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...
随机推荐
- ubuntu 13.10 64bit装BeyondCompare
1. Beyond Compare官网下载amd-64位的,安装失败,依赖于ia32-libs,但是这个文件已经不在源里了: 2. 官网下载tar.gz源码包,解压安装失败: 3. 直接装32位的,可 ...
- LeeCode 1-Two Sum
Two Sum Total Accepted: 125096 Total Submissions: 705262 Question Solution Given an array of integer ...
- lintcode :Invert Binary Tree 翻转二叉树
题目: 翻转二叉树 翻转一棵二叉树 样例 1 1 / \ / \ 2 3 => 3 2 / \ 4 4 挑战 递归固然可行,能否写个非递归的? 解题: 递归比较简单,非递归待补充 Java程序: ...
- lintcode :Count 1 in Binary 二进制中有多少个1
题目: 二进制中有多少个1 49% 通过 计算在一个 32 位的整数的二进制表式中有多少个 1. 样例 给定 32 (100000),返回 1 给定 5 (101),返回 2 给定 1023 (111 ...
- POJ1248 Safecracker
第一次写DFS的程序,虽然是个水题.1. 学了memset2. 可以存下来A-Z的各个次方的结果3. 可以排序优化4. 我用了t[0]==0来判断是否有解,也可设个flag5. 用了递归,也可用五层循 ...
- 转载CSDN (MVC WebAPI 三层分布式框架开发)
前言:SOA(面向服务的架构)是目前企业应用开发过程中普遍采用的技术,基于MVC WebAPI三层分布式框架开发,以此适用于企业信息系统的业务处理,是本文论述的重点.此外,插件技术的应用,富客户端JQ ...
- 函数执行到return就结束了
遇到return,函数就结束了,不会往下执行 测试: class User { String name; int age; boolean fun1(int i){ if(i==1){ return ...
- 对原生态jdbc程序中问题总结
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import ...
- Windows下Sublime Text 默认打开方式问题解决办法
注册表的解决办法: 删除 HKEY_CURRENT_USER\Software\Classes\Applications下的Sublime_Text.exe项.你就发现可以设置为默认打开方式了
- 257. Binary Tree Paths
题目: Given a binary tree, return all root-to-leaf paths. For example, given the following binary tree ...