【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)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...
随机推荐
- ConcurrentHashMap使用示例
ConcurrentHashMap使用示例 发表于2年前(2013-07-12 19:05) 阅读(3660) | 评论(0) 25人收藏此文章, 我要收藏 赞5 如何快速提高你的薪资?-实力拍“ ...
- 通过数据库表自动生成POJO(JavaBean)对象
主类: package bqw.tool; import java.util.ResourceBundle;import java.sql.DriverManager;import java.sql. ...
- 即时通信Spark安装和配置
spark:Cross-platform real-time collaboration client optimized for business and organizations.Spark i ...
- js、javascript正则表达式验证身份证号码
function isCardNo(card) { // 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X var reg = /(^\d{1 ...
- Java泛型 通配符? extends与super
Java 泛型 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 <? super T> 表示类型下界(Java ...
- Windows 7下配置JDK环境变量和Java环境变量配置
下面来介绍一下Java环境变量配置,是在Windows 7下配置JDK环境变量. 方法/步骤 1 安装JDK,安装过程中可以自定义安装目录等信息,例如我们选择安装目录为:C:\Program Fil ...
- spring+hibernate+Struts2 整合(全注解及注意事项)
最近帮同学做毕设,一个物流管理系统,一个点餐系统,用注解开发起来还是很快的,就是刚开始搭环境费了点事,今天把物流管理系统的一部分跟环境都贴出来,有什么不足的,请大神不吝赐教. 1.结构如下 2.jar ...
- npm在项目目录安装插件需要使用sudo
今天使用node的npm安装插件的时候遇到一个问题,那就是在项目目录里面安装插件的时候,必须使用超级用户(sudo)执行才会安装成功,否则会报如下错误: 以安装 gulp-uglify 为例 $ np ...
- UVALive 4452 The Ministers' Major Mess(2-sat)
2-sat.又学到了一种使用的方法:当确定选择某中状态A时,从它的对立状态A^1引一条边add(A^1,A),从而使凡是dfs经过对立状态,必然return false:即保证若存在一种可能性,必然是 ...
- union与struct以及大小端
两者的区别: 1. 共用体和结构体都是由多个不同的数据类型成员组成, 但在任何同一时刻, 共用体只存放了一个被选中的成员, 而结构体的所有成员都存在. 2. 对于共用体的不同成员赋值, 将会对其它 ...