传统认识认为:如果我们没有定义一个自己的拷贝构造函数,编译器会帮助我们合成 一个拷贝构造函数。
结论:这个合成的拷贝构造函数,也是在 必要的时候才会被编译器合成出来。
示例代码
class A
{
public:
int m_test;
};
int main()
{
A a1;
a1.m_test=20;
A a2 = a1;
return 1;
}
加断点,F5调试,F10向下走一步(visual studio中单步调试)
a2的m_test确实被赋给了a1的m_test的值,传统我们以为此处编译器帮我们创建了拷贝构造函数,
但是我们再去查看一下.obj文件(使用dumpbin,同上篇)
可以看出,此时编译器并没有帮我们合成一个拷贝构造函数,并且此时根本也找不到其他构造函数
于是有两个疑问:
1.既然没有拷贝构造函数, A a2 = a1;语句中是如何赋值的呢?
《深度探索C++对象模型》49页告诉我们:这其实是编译器初始化的一种手法,以所谓的default memberwise initialization手法完成的,对于内置的数据类型成员和派生的数据类型成员它会采取这种方式,如int,一个指针或数组,此种手法是编译器不合成拷贝构造函数的情况下就帮我们把赋值这件事完成了
2.也没有其他构造函数,那我们传统认为的在构建对象时总会调用构造函数的说法就不一定?(我的思考,暂时也没找到确切答案,不敢肯定的回答)
我们再来看一下,含有一个对象数据成员的类会如何呢?
class B
{
public:
int m_test2;
};
class A
{
public:
int m_test1;
B b;
};
int main()
{
A a1;
a1.m_test1 = 20;
a1.b.m_test2 = 100;
A a2 = a1;
return 1;
}
调试结果:
可以看到a2中的m_test2已经被赋值了,但是此时也未找到拷贝构造函数
《深度探索C++对象模型》49页告诉我们:还是与上面同理,这是编译器内部的手法,但是此时不会拷贝该类的对象,会以递归的方式进行memberwise initialization,也就是说,对于a1而言,类A没有拷贝构造函数,语句A a2 = a1;以memberwise initialization方式完成拷贝,但是在复制过程中又遇到类B的赋值,类B也没有拷贝构造函数,也以memberwise initialization方式完成拷贝,所以逐层递归
那编译器在什么情况下会帮助我们合成出拷贝构造函数来呢?那这个编译器合成出来的拷贝构造函数又要干什么事情呢?(一切前提都是我们自己没写拷贝构造函数)
(1)如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量类型并且该类类型含有拷贝构造函数,当代码中有涉及到类A的拷贝构造时,编译器就会为类A合成一个拷贝构造函数。
合成目的:为了调用对象成员的构造函数
代码示例:
#include <iostream>
using namespace std;
class C
{
public:
C(const C& c)
{
cout << "类C的拷贝构造函数被调用了";
}
};
class A
{
public:
int m_test1;
C c;
};
int main()
{
A a1;
a1.m_test1 = 20;
A a2 = a1;
return 1;
}
代码如果写成这样,会报错:
报错分析:因为我们显式定义了C的构造函数(C的拷贝构造函数),所以编译器不会再为C合成默认构造函数,而第26行语句会调用C的默认构造函数就会找不到而报错
所以此处我们给类C添加一个缺省的构造函数
#include <iostream>
using namespace std;
class C
{
public:
C(const C& c)
{
cout << "类C的拷贝构造函数被调用了";
}
C()
{}
};
class A
{
public:
int m_test1;
C c;
};
int main()
{
A a1;
a1.m_test1 = 20;
A a2 = a1;
return 1;
}
运行结果:
查看.obj文件:
编译器确实为我们合成了A的拷贝构造函数,并且在其中添加了调用C的拷贝构造函数的代码,并且此时对于普通变量的memberwise initialization方式的内部手法仍然存在,并不是在A的拷贝构造函数中为其添加赋值代码
结论:
编译器合成的拷贝构造函数往往都是干一些特殊的事情。如果只是一些类成员变量值的拷贝这些事,编译器是不用专门合成出拷贝构造函数来干的,编译器内部就干了;
待补充。。。。。。