1.default constructor
若为显式定义默认构造函数,编译会在下面四种情况下隐式定义默认构造函数:
(1)某个数据成员对应的类型有默认构造函数;
(2)基类有默认构造函数;
(3)有虚函数表指针;
(4)有虚成员数据块指针(虚继承);
隐式定义的默认构造函数只调用(1)(2)的默认构造函数和初始化(3)(4)的指针,不会再做任何其他的事。
在显示定义的任何构造函数中,如果(1)(2)没有通过初始化列表指定,则也会由编译器隐式调用对应的默认构造函数,而(3)(4)的初始化是必定执行的。
我觉得这个隐式默认构造函数真的很废啊,没做什么有用的事,反而可能造成程序员的疏忽,还不如没有的好。
2.copy constructor
复制构造函数可以是多参数的,只要第二及以后的参数有默认值就行,例如:
X X(const X& x, int i=0, char* p=NULL);
在类不是bitwise类型的时候,如果没有显示定义copy ctor,则编译器隐式定义copy ctor。以下四种情况,类不是bitwise类型:
(1)某个数据成员对应的类型有copy ctor;
(2)基类有copy ctor;
(3)有虚函数表指针;
(4)有虚成员数据块指针(虚继承);
bitwise类型的类在拷贝时,直接是内存数据的复制。
3.NRV优化
具体参考下文,困扰我的是否进行NRV优化和是否有copy ctor直接的关系在文中也有解释
http://www.189works.com/article-39096-1.html
在C++中,函数返回整数或指针是通过eax寄存器进行传递的,理解起来比较简单。 但是返回对象或结构体一直是令人感到困惑的问题。今天我整理了一下,将整个返回过程写下来,以作备用。
还是先通过一个例子来理解这个问题: 首先,定义一个类Vector:
然后定义函数add()对Vector对象进行操作:
现在的问题是: 如果调用如下语句: 请问从a, b传入函数开始,一共创建了多少个对象?
在通常情况下我们会做出如下分析: 1. 在add()函数中创建对象v。 2. 函数返回,创建一个临时变量__temp0,并将v的值拷贝到__temp0中。 3. 最后创建对象c,通过操作符=,将__temp0中的对象拷贝到c中。
但其实,我们会在后面看到,整个过程就只创建了1个对象:c。
为了更清晰的分析整个调用过程,我们为Vector加上默认构造函数和拷贝构造函数,并增加一个静态变量count用于统计构造函数调用次数:
然后在main()函数中写上调用代码:
使用cl编译。 (注:Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86, Microsoft (R) Incremental Linker Version 10.00.40219.01) 完成后,运行程序,得到如下结果:
由此可知,在没有优化的情况下,整个调用过程共创建了两个对象: 即:c和__temp0.
整个调用过程伪代码如下: 首先add()函数被编译器看做: 而调用代码同时被修改为:
现在就可以理解输出结果了吧。 这里要强调一点,看到”=”并不等于调用了Operator=()的代码,以下三种情况其实是等效的,都只调用了拷贝构造函数:
最精彩的部分在于,如果你用
编译代码,使优化达到最大,再次运行,得到如下结果:
这次,只调用了默认构造函数。这样的修改被称作Named Return Value(NRV) Optimization。 什么是NRV优化呢,顾名思义,就是保存返回值的变量不再使用没名没姓的__temp0这样的东西了,而是直接把c作为返回变量,因此应该将NRV翻译为“有名字的返回变量”吧,侯捷翻译的《深入探索C++对象模型》居然把它称为“具名数值”,真是不知所云。 言归正传,NVR优化的伪代码如下: NVR优化的最大好处就是不会再去调用那次多余拷贝构造函数了(把__temp0拷贝到c),因此《深入探索C++对象模型》67页最下面才会说第一版没有拷贝构造函数,所以不能进行优化。其实是指优化的意义不大,或者说没有什么可优化的。 这是经过优化后的add函数汇编代码: (注意:我尝试把Vector的拷贝构造函数删掉,同样生成了上面这段代码(一个字节都没变),因此我推测,拷贝构造函数并不是触发NRV优化的条件了,Lippman的书可能有点过时了。) 但是这样带来的坏处是,如果你在拷贝构造函数里面放上与拷贝无关的代码,比如我放入的printf和count++,那么这些东西就不会被调用了,产生优化前后代码不一致问题。所以大家要在此注意一下。 |