【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)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...
随机推荐
- linux上tomcat安装
转载:http://my.oschina.net/zmf/blog/309484 tomcat安装 下载后解压:tar -zxvf apache-tomcat-7.0.28 移动到/usr/local ...
- 常用Shell的路径
#define REG_SHELL "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\S ...
- 如何在Android应用程序中使用传感器模拟器SensorSimulator
原文地址; 如何在Android应用程序中使用传感器模拟器 - 移动平台应用软件开发技术 - 博客频道 - CSDN.NET http://blog.csdn.net/pku_android/arti ...
- 实例学习写Makefile文件
目录 0. 扫盲 1. 编译,链接 2. Makefile文件执行 3. Makefile书写规则 4. 案例 5. Makefile是如何工作的 6. 拔高,参考 0. 扫盲 Linux 环境下的程 ...
- Java API —— File类
1.File类的概述 文件和目录路径名的抽象表示形式,创建File对象后,仅仅是一个路径的表示,不代码具体的事物一定是存在的. 2.构造方法 · public File ...
- PHP输出缓冲控制- Output Control 函数应用详解
说到输出缓冲,首先要说的是一个叫做缓冲器(buffer)的东西.举个简单的例子说明他的作用:我们在编辑一篇文档时,在我们没有保存之前,系统是不会向磁盘写入的,而是写到buffer中,当buffer写满 ...
- 创建支持复杂脚本Complex Scripts的WINCE6.0系统
如果要创建支持复杂脚本(Complex Scripts)的系统,我们需要完成下面一系列步骤来确保系统包含所有需要支持的具体区域设置 (locale–specific). 1. 选择intern ...
- [HIHO1318]非法二进制(动态规划)
题目链接:http://hihocoder.com/problemset/problem/1318 题意:是个dp题.考虑二进制数为i位的时候,无非有两种情况:新添加的一位为0或者1. 为0的时候,那 ...
- c# webbrowser 清除当前网站 cookie
//这个方法可以创建一个清除当前页面下指定域的所有cookie //必须是可以访问的域,比如你访问的是qq.com,那么可以清除www.qq.com,qzone.qq.com等页面的cookie // ...
- 函数buf_page_create
/********************************************************************//** Initializes a page to the ...