《深度探索C++对象模型》第二章 构造函数语意学

Default Constructor的构建操作
default constructors在需要的时候被编译器产生。
例:

clas Foo
{
public:
    int val;
    Foo*next;
}
void foo_bar()
{
 Foo bar;
 if(bar.val||bar.next)//如果两个数据有一个存在的话
 {
 //doing something
 }
}

上述的代码情况中,并不会生成一个deafult constructor。
需要注意的地方是:
全局的object内存在被激活时会清0,而局部对象配置于堆栈中,不一定会清0,它们的内容是内存上次被使用的遗留值。
接下来分4种情况讨论编译器会产生default constructor的情况:

①带有默认构造器的member class object
如果一个类中包含一个成员对象,这个对象含有default constructor,那么这个类隐含生成的默认构造器就是有用的(nontrivial),这个合成造作只有真正需要被调用的时候才会产生。
那么一个问题产生了,在C++不同的编译模块中,怎么避免合成出多个default constructor呢?解决办法就是把合成的默认构造器,拷贝构造器,喜购气,拷贝构造操作符都以inline的方式完成,具有静态链接,不会被档案外的人看到,如果函数太复杂,会合成一个显式的非内联静态实体。被合成的default constructor是满足编译器的需要而不是程序员的需要!!
如果一个类A内涵一个或一个以上的member class object,那么A的每一个构造器都会调用子类的默认构造器,编译器会扩展已经存在的构造器,在其中安插一些代码,使得用户代码在被执行前,先调用必要的默认构造器。

②带有default constructor的base class
如果子类的父类有默认构造器,那么子类生成的默认构造器会自动调用父类的默认构造器,对于后继承的类而言,这个合成的构造器和默认构造器没什么区别。

一个带有virtual function的class
1.一个class声明了或者继承了一个虚函数。
2.class派生自一个继承串链中,其中有虚基类,会发生扩张操作:
①一个虚函数表会被编译器产生出来,内放有类的虚函数地址。
②每一个类对象中,一个额外的指针成员会被编译器合成出来,内含相关的虚表地址。

带有一个virtual base class的class

总结:一共有4中情况会导致”编译器必须为未声明constructor的类合成一个默认的构造器“,被合成出来的构造器只能满足编译器的需要,而其它情况并不会被合成出来。
而在合成出来的默认构造器中,只有基类子对象(base class subobject)和成员类对象会被初始化,其它的nonstatic data members比如int,float都不会被初始化。
C++新手的两个误解
1.任何类如果没有定义默认构造器,就会被合成出一个来。
2.合成出来的默认构造器会被明确设定”class内每一个数据成员的默认值“。
这两个没一个是对的!!


Copy constructor的构建操作(default memberwise initialization)
如果一个类没有提供一个显式的拷贝构造器,当一个对象X1以另一个相同类的对象X2为初值时,会以default memberwise initialization,也就是把每一个内建的或者派生的数据成员的值拷贝到另个一个对象,而其中的member class object并不会拷贝,而是以递归的方式bitwise initialization。(bitwise copy semantics 位逐次拷贝语意)
什么时候不会展现出bitwise呢?
1.类内含有一个成员对象,而后者含有一个拷贝构造器时候(无论是自己设计的还是编译器合成的)
2.当类继承自一个基类,而基类含有一个拷贝构造器时。
3.当类声明了一个或者多个虚函数时。
4.当类派生自一个继承串链,其中有一个虚基类时。
其中,1,2会将成员或基类的拷贝构造器调用操作安插在合成的拷贝构造器中,而3,4比较复杂,书的后面章节有继续的介绍。

NRV(named return value)优化

X bar()
{
X xx;
return xx;
}
//优化之后
void bar(X&__result)
{
__result.X::X();
reuturn;
}
//如果不优化
void bar(X &__result)
{
X xx;
xx.X::X();
__result.X::X(xx);
return ;
}

可以减少拷贝构造的次数


X xx0(1024);//1号
X xx1=X(1024);//2号
X xx2=(X)1024;//3号

以上程序,2号,3号多了依次构造,还有依次析构来销毁临时对象,其中的步骤:
1.将暂时性的obejct设置为1024。
2.将暂时性的object以拷贝建构的方式作为explicit object初值


如果一个类内含有一个或一个以上的虚函数,或有虚基类,那么使用memcpy或者memset会使得被”编译器产生的内部members“的初值被改写。


Member initialization list成员初始化列表
下面就4种情况必须使用成员初始化列表:
1.当初始化一个reference member时。
2.当初始化一个const member时。
3.当调用一个base class的构造器,而它拥有一组参数时。
4.当调用一个成员类的构造器,而它拥有一组参数时。
在这4种情况中,程序可以编译,但是效率不高。

Class word
{
string name;
int n;
word(){
name=0;//会产生副本name=string(0);
n=0;//n=int(0);
}
}

list中的项目次序是由class中的成员声明次序决定,不是由初始化列表中的排序次序决定

class X
{
int i;
int j;
public:
X(int val):j(val),i(j)
{
//do something.....
}//错误!i会先初始化
}

初始化成员列表的执行次序一定会在explicit user code之前!

https://github.com/li-zheng-hao
原文地址:https://www.cnblogs.com/lizhenghao126/p/11053764.html