蓝图
码农没干
-->编译器代劳
-->编译器什么时候动手
-->编译器的劳动产出的特殊之处
-->编译器的copy assigment的坑
空类
作者提出了一个所谓的“空类”的概念,对于普通码农来讲,空结构体可能更易理解,比如这样子:
struct empty_str {};
没有任何成员的结构体应该就是“空结构体”了,用GCC编译可以发现size是0,用G++编译发现size是1,不纠结这个0和1了,和主题无关。
那么对应的C++的空类大概也是这样子吗?
class EmptyClass{};
G++编译,计算size,这个所谓的“空类”size为1。
作者说:
编译之前,在代码文件里这是一个空类;
编译之后,这个在目标文件里就不是一个纯粹的空类,用作者的话说:在编译的时候被C++处理过了。
虽然我们在代码层面声明了我们以为的“空类”,但是在编译的时候C++(也就是编译器)自动的给声明了“编译器版本”(对应码农的自己实现版本)的(而且是public且inline的):
- copy构造函数;
- default构造函数;
- copy assignment操作符;
- 析构函数
编译器创建这四个函数的时机
码农不声明,编译器给声明,但是这仅仅是声明,并不保证这些函数真的就会被创建。因为C/C++仅仅声明一个函数,只要不调用,不定义函数也是可以的,比如这样是完全没问题的:
int fakeFunc(int a);
int main()
{
cout << "do not define fakeFunc" <<endl;
return 0;
}
或者这样也是可以的,只要保证不会实际调用:
class TstClas {
public:
int a;
//只有声明,不定义,如果调用,在连接阶段会报错
TstClas(const TstClas&);
TstClas& operator=(const TstClas&);
}
int main()
{
TstClas theTst;
theTst.a = 1;
return 0;
}
那编译器什么时候创建这些函数呢,答案是当“编译器需要的时候”,比如编译在编译的时候,发现某一句代码需要调用所谓的“空类”的copy构造函数,但码农又没有定义copy构造函数,于是编译器赶紧自己写了一个;发现需要调用构造函数确没有定义构造函数,编译器赶紧就摸出了一个default构造函数。
Empty_class e1; //编译器:没有构造函数和析构函数哎,我自己弄一个吧
Empty_class e2(e1); //编译器:copy构造函数也得自己弄
e2=e1; //编译器:不省心 copy assignment操作符也得DIY了
编译器实现的这四个函数的特点
default构造和析构函数:调用base classes和non-static成员变量的构造函数和析构函数;
析构函数:编译器默认产出non-virtual,除非base class自身声明有virtual析构函数;
copy构造函数和copy assignment操作符:单纯的将来源对象的每个non-static成员变量复制到目标对象的对应成员变量中;
copy构造函数和copy assigment操作符所谓的“copy”
这里的“copy”一般解释是“将来源对象的每个non-static成员变量复制到目标对象的对应成员变量中”,但是对于不同的成员情况,有两种处理方式:
成员是具有copy构造函数的标准类或码农自己实现的类:调用成员的copy构造函数;
成员是内置类型如int,char *p等:直接按照bits复制;
显然,是浅copy。
copy assigment操作符的两个大坑
如果码农自己没有实现copy assigment操作符,编译器给提供一个,但是存在两个大坑导致编译器“提供copy assigment操作符”失败“表现出来就是编译不过”。
坑一:class A内含reference成员和const成员:必然的,copy不了,reference成员和const成员不允许改变初始值;
坑二:class A的base class中实现了一个private copy assigment操作符:class A的copy assigment发现无法调用bass class的copy assigment,两手一摊:“这事儿我干不了”;