构造函数与初始化
构造函数的初始化作用
构造函数特点
- 和类同名
- 没有返回值
- 形参列表可能为空
- 函数体可能为空
- 可以重载
- 不能声明为 const
合成默认构造函数
合成默认构造函数将按照如下规则初始化类的数据成员:
-
如果存在类内的初始值,用它来初始化成员,即C++11新标准中新增的类内初始化。
-
否则,执行默认初始化该成员。
什么是类内初始化
指导规则:通常情况下,不应该在类内部初始化成员!!无论是否为静态、是否为常量、是否为int等!!统统不建议在类内初始化,因为本质上类只是声明,并不分配内存,而初始化会分配内存,类内初始化会将两个过程混在一起!
- 对于静态成员
-
静态成员必须为字面值常量类型:constexpr。
-
给静态成员提供的初始值,必须为常量表达式。
字面值常量类型constexpr,与const类型是不一样的
构造函数初始值列表
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n),revenue(p*n)
成员初始化的顺序与他们在类定义中出现的顺序一致。
=default的含义
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上 = default
来要求编译器生成默认构造函数。
委托构造函数
一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。
class Sales_data
{
public:
//非委托构造函数使用对应的实参初始化成员
Sales_data(std::string s, unsigned cnt, double price):bookNo(s), units_sold(cnt), revenue(cnt * price) {}
//其余构造函数全都委托给另一个构造函数
Sales_data(): Sales_data("", 0 , 0) {}
Sales_data(std::string s): Sales_data(s, 0, 0) {}
Sales_data(std::istream &is): Sales_data(){read(is, *this);}
}
必须合成默认构造函数的情况,《Inside the C++ Model》总结了四种情况:
-
含有类对象数据成员,该类对象类型有默认构造函数:
class A { public: B var; void fun(){} };
-
类的基类提供了默认的构造函数
class A:public C
-
类内定义了虚函数
virtual void fun(){}
C++为了实现多态机制,需要为类维护一个虚函数表(vftable),而每个该类的对象都保存一个指向该虚函数表的一个指针(一般保存在对象最开始的四个四节处)。编译器为A生成构造函数,其实不为别的,就为了保证它定义的对象都要正常初始化这个虚函数表的指针(vfptr)!
-
类使用了虚继承
class A:public virtual C
这次构造函数也初始化了一张表——vbtable,这张表叫虚基类表,它记录了类继承的所有的虚基类子对象在本类定义的对象内的偏移位置。为了保证虚继承机制的正确工作,对象必须在初始化阶段维护一个指向该表的一个指针,称为虚表指针(vbptr)。编译器因为它提供A的默认构造函数的理由和虚函数时类似。
它们遵循着一个最根本的原则:只有编译器不得不为这个类生成函数的时候(nontrival),编译器才会真正的生成它。
参考文章不要被C++“自动生成”所蒙骗
构造顺序
-
构造子类构造函数的参数
-
子类调用基类构造函数
-
基类设置vptr
-
基类初始化列表内容进行构造
-
基类函数体调用
-
子类设置vptr
-
子类初始化列表内容进行构造
-
子类构造函数体调用
-
已使用 OneNote 创建。