第2章 构造函数语意学

第2章 构造函数语意学

2.1 默认构造函数的构造

考虑如下代码

class Foo {
public: 
	int val;
	Foo *pnext;
}

void foo_bar() {
	// 程序要求bar's members都被清为0
	Foo bar;
	if (bar.val || bar.pnext) 
		// ... don something	
	// ...
}

上述代码是否会合成默认的构造函数? 这里有两个问题要弄明白:

  • 编译器需要
  • 程序需要: 上述代码就是"程序需要", 编译器会声明一个构造函数, 但是并不会合成出来, 所以还是没有构造函数, 在这种情况下为程序执行初始化应该是程序员的责任

那么在未来是否会合成默认构造函数:

  • 在C++ Annotated Reference Mannual(ARM)中: 只有在编译器需要需要时才会合成默认的构造函数
  • C++ Standard: 如果没有任何用户声明的构造函数(注意: 是任何声明的构造函数, 包括拷贝构造函数), 那么就会有一个默认构造函数被隐式的声明出来, 但是这样被声明出来的默认构造函数是trivial(浅薄无能, 就是没啥用)constructor. 只有当一个默认构造函数是nontrivial时, 才会被合成出来. 所以, 上面的代码会声明一个trivial的默认构造函数, 但是因为是trivial, 所以不会合成出来, 编译器会报错

有4种情况, 会使编译器为没有声明构造函数的类合成一个默认构造函数, 即nontrivial的默认构造函数

  1. 类包含带有默认构造函数的成员, 合成出来的默认构造函数并不会初始化本类的其他成员, 初始化其他成员是程序员的责任
  2. 类是由带有默认构造函数的基类所派生出来的
  3. 类中带有一个虚函数
      1. class声明或继承了一个虚函数
      2. class派生自一个继承串链, 其中有一个或更多的虚基类
  4. 类中带有一个虚基类 有些疑问

被合成出来的构造函数只能满足编译器(而非程序)的需要, 它之所以能够完成任务, 是借着"调用成员对象或基类的默认构造函数"或是"为每一个对象初始化其虚函数机制或虚基类机制"而完成的

在合成的默认构造函数中, 只有基类子对象和类成员对象会被初始化. 所有其他的nonstatic数据成员(如整数, 整数指针, 整数数组等等)都不会初始化. 这些初始化操作对程序而言或许有需要, 但对编译器则是非必要. 如果程序需要一个"把某指针设为0"的默认构造函数, 那么提供它的人应该是程序员

总结就是都会声明, 但是会不会合成又是另一回事了, 取决于是trivial还是nontrivial

2.2 拷贝构造函数的构造初始化

有3种情况会以一个对象内容作为另一个对象的初值, 1. 显示以一个对象内容作为另一个对象的初值(初始化, 而不是单纯等号操作); 2. 当对象做参数交给某个函数(做形参, 非引用指针形式); 3. 对象做返回值(非引用指针形势)

  • 默认逐成员初始化(Default Memberwise Initialization)
  • 逐成员初始化(Memberwise Initialization)
  • 位逐次拷贝(Bitwise Copy Semantic)
// 以下声明展现了bitwide copy semantic
class Word {
public:
	Word(const char *);
	~Word() { delete[] str; };
	// ...
private:
	int cnt;
	char *str;
}

如果类X没有显式的拷贝构造函数, 那么在用一个类X的对象a初始化这个类的对象b时, 内部采用的就是默认逐位成员初始化. 具体来讲, 就是把a的数据一个个单独拷贝到b中. 如果类X里面还包含有成员类对象(Member Class Object), 如类Y的对象, 那么此时就不会把a的成员对象拷贝到b中, 而是递归的进行逐成员初始化, 逐成员初始化用的就是逐位拷贝拷贝构造函数

就像默认拷贝构造函数一样, C++ Standard上说, 如果类没有声明一个拷贝构造函数, 就会有隐式的声明或隐式的定义出现. 和以前一样, C++ Standard把拷贝构造函数区分为trivial和nontrivial两用, 只有nontrivial的实例才会被合成于程序之中. 如果展现出"bitwise copy semantic"(位逐次拷贝语义), 那么拷贝构造函数就是trivial的
qu
如果一个类没有定义显示的拷贝构造函数, 那么编译器是否会为其合成取决于类是否展现"位逐次拷贝":

  • 如果类展现出"位逐次拷贝", 则编译器不需要合成一个默认的拷贝构造函数
  • 如果类不展现"位逐次拷贝", 则编译器必须合成一个默认的拷贝构造函数, 不展现"位逐次拷贝"的情况有以下4种:
      1. 类包含具有拷贝构造函数的成员
      2. 类继承自一个具有拷贝构造函数的基类
      3. 类中声明一个或多个虚函数
      4. 类派生自一个继承串链, 其中有一个或多个虚基类

前2种情况中, 编译器必须将成员或基类的"拷贝构造函数调用操作"安插到被合成的拷贝构造函数中

第3种情况不展现出"位逐次拷贝"是因为需要正确的处理虚函数指针vptr. (1)如果使用子类的一个对象初始化另一个子类的对象, 可以直接靠"位逐次拷贝"完成; (2)如果用一个子类对象初始化一个父类对象, 会发生切割行为, 父类对象的虚函数指针必须指向父类的虚函数表vtlb, 如果使用"位逐次拷贝", 那么父类的虚函数指针会执行子类的vtlb

合成出来的ZooAnimal copy constructor会显式设定object的vptr指向ZooAnimal class的virtual table, 而不是直接从右手边的class object中将其vptr现值拷贝过来. 参考第1章最后一句话

第4中情况不展现"位逐次拷贝"是因为虚基类对象部分能够正确初始化. (1)如果使用虚基类子类的一个对象, 初始化虚基类子类另一个对象, 那么"位逐次拷贝"绰绰有余; (2)但如果企图以一个虚基类子类的子类对象, 初始化一个虚基类子类的对象, 编译器就必须判断"后续当程序员企图存取其虚基类子对象能否正确执行", 因此必须合成一个拷贝构造函数, 安插一些代码以设定虚基类指针和偏移量的初始值(或只是简单的确定它没有被抹销)

在下面的情况下, 编译器无法知道"位逐次拷贝"是否还保持着, 因为他无法知道Raccoon指针是否指向一个真正的Raccoon对象或者是指向一个Raccoon的子类对象

Raccoon *ptr;
Raccoon little_critter = *ptr;

总结就是, 按照C++ Standard, 如果用户没有声明, 就会隐式的声明一个, 但是会不会合成取决于声明出的是trivial还是nontrivial, 拷贝的动作都会有, 就在于会不会合成拷贝构造函数(成员都是基础数据类型就不用拷贝函数了, 因此就不用合成了)

2.3 程序转换语意学

class Test {
public:
	Test() { cout << "默认构造函数" << endl; }
	Test(const Test &obj) { cout << "拷贝构造函数" << endl; }
};

Test foo() {
	Test t;
	return t;
}

int main() {
	// 输出默认构造函数
	Test t = foo();	
	return 0;
}

拷贝构造函数的应用, 迫使编译器多多少少对程序代码做部分转化. 尤其是当一个函数以值的方式传回一个类对象, 而该类有一个拷贝构造函数(无论是显式定义出来还是合成出来)时, 这将导致深奥的程序转化为-->无论在函数的定义上还是在使用上. 此外, 编译器将拷贝构造的调用操作优化, 以一个额外的第一参数(数值被直接存放其中)取代NRV(named return value). 如果了解那些转换, 已经拷贝构造函数优化后的可能状态, 就比较能够控制程序的执行效率

2.4 成员初始化列表

为使程序能够被正确编译, 在下列情况中必须使用初始化列表:

  1. 当初始化一个引用成员时
  2. 当初始化一个常成员时
  3. 当调用一个基类的构造函数, 而它拥有一组参数时
  4. 当调用一个类类型成员的构造函数, 而它拥有一组参数时

编译器会对初始化列表一一处理, 以反应出成员的声明顺序. 它会安插一些代码到构造函数体内, 并置于任何用户代码(explicit user code)之前

和默认构造函数, 拷贝构造函数相关的问题: 是否可以使用memset来初始化一个对象, 使用memcpy来拷贝一个对象?

只有在"class不含任何由编译器产生的members"时才能有效运行. 如果class声明一个或多个以上的virtual functions, 或者内含一个virtu base class, 那么上述函数将会导致哪些"被编译器产生的内部members"的初值改写

class Shape {
public:
	// 会改变内部vptr
	Shape() { memset(this, 0, sizeof(Shape)); }
	// 当传入一个子类对象的地址时, vptr会指向子类的虚函数表
	Shape(const Shape &rhs) { memcpy(this, & rhs, sizeof(Shape)); }
	virtual ~Shape(); 
}
原文地址:https://www.cnblogs.com/hesper/p/10588810.html