背景
作为一个C++小白,最近在看深度解析对象模型的时候,发现一个很久以来的认知错误:编译器会为没有定义构造函数的class生成一个默认构造函数。其实这个观点是错误
的,编译器只会在四种情况下生成。
相关知识
- 一定要明确一个事情,就是基础数据类型没有默认构造函数,因为int, float,char这些不是class/struct,基础数据类型就是基础数据类型,不要自己强行脑补为类或者结构。
- 构造函数花括号里不能用小括号初始化任何成员变量,凡是想初始化都放在初始化列表里就好了
四种情况
情况一
当成员变量中有默认构造函数时,举例说明:
class Test
{
public:
int a;
float b;
};
int main()
{
Test t;
int c = t.a +1;
return 0;
}
这种情况下编译器不会生成默认构造函数,因为int, float不是对象,没有默认构造函数。可以根据汇编代码发现确实没有。
class Test
{
public:
int a;
float b;
string s;
};
可以看到一旦加入string的话就会出现默认构造函数了。
情况二
该类的基类有默认构造函数
情况三
类中存在虚函数,那么C++编译器会为你生成默认构造函数,以初始化虚表(虚函数表vftable)。
情况四
存在虚基类,那么C++编译器会为你生成默认构造函数,以初始化虚基类表(vbtable)。
生成默认构造函数
- 首先生成一个空壳子
inline Construct()
{}
- 补充初始化列表
这里是一个知识点,list中的顺序是按声明顺序来的,跟你怎么写代码没啥关系
这里初始化列表会把所有的成员变量的默认构造函数依次加入进来,下面是一个伪代码的例子:
class Test
{
public:
float d;
string A;
Student B;
Teacher C;
int e;
}
inline Test() {A(), B(), C(), e} //这里e不会初始化,只是分配内存
{}
- 把初始化列表挪到函数大括号中
inline Test()
{
A();
B();
C();
e;
}
**可以看到,自动生成的默认构造函数其实并不会给所有成员都初始化**
。此外,这里可能会觉得第三步和第二步可以合在一起,其实拆开是为了后面程序员自己添加构造函数时,编译器补充函数信息逻辑更清楚。
自定义默认构造函数
对于自定义默认构造函数,编译器有三个原则:
- 插入的代码只能在用户实现的代码前
- 补充的代码符合成员变量的声明顺序
- 基础数据类型依然不是编译器的考虑的范围
方式一、初始化列表为空
我们根据上面已经知道,编译器会自动生成构造函数,但是如果程序员自己提供了构造函数怎么处理?
class Test
{
public:
float d;
string A;
Student B;
Teacher C;
int e;
}
Test()
Test::Test()
{
B("student");
}
- 补充初始化列表
Test()
Test::Test(){d, A(), B(), C(), e}
{
//B("student"); //编译器会报错:成员变量**对象**(不是基础数据)必须在构造函数初始化列表里初始化
B = "student";
}
- 挪到函数中
Test()
Test::Test(){}
{
d;
A();
B();
C();
e;
B = "student";// 等效于:tmp("student"), B=tmp;
}
方式一、初始化列表不为空
class Test
{
public:
float d;
string A;
Student B;
Teacher C;
int e;
}
Test()
Test::Test():B("student")
{
}
- 补充初始化列表
Test()
Test::Test(){d, A(), B("student"), C(), e}
{
}
- 挪到函数中
Test()
Test::Test(){}
{
d;
A();
B("student");
C();
e;
}
根据这两个小例子可以好好的体会上面的三个原则