阅读侯捷老师翻译的深度探索C++对象模型时所进行的相关测试以及自己的理解
一、浅讲NRV优化
NRV,即Named Result Value,指的是编译器对于我们所写代码的一种优化,如:
B pplus()
{
B f;
return f;
}
我们知道在一些没有NRV优化的编译器中,对于上述的代码在执行时,离开函数作用域时,会调用函数作用域内f的析构函数,也就是说我们在主函数内假定有代码B d=pplus();此时主函数结束时,总共会有两次B的析构函数(假定主函数中B的对象只有d)。而NRV优化会帮助我们在编译器层面上进行优化。下面是主要的测试内容以及分析(均在GCC环境下)
二、测试代码
#include<iostream>
using namespace std;
class A {
public:
int x;
int y;
public:
A(int x,int y):x(x),y(y){cout<<"direct construct"<<endl;}
A(const A&a){this->x=a.x;this->y=a.y;cout<<"copy construct"<<endl;}
~A(){cout<<"delete"<<endl;}
};
A pplus()
{
A f(1,1);
return f;
}
int main()
{
A aa=pplus();
return 0;
}
三、运行结果
四、结果分析以及解释
NRV优化对于上述代码中,所执行的操作相当于
void pplus(A& _result)
{
// 调用__result相应的构造函数
__result.A::A(1,1);
// 处理__result
return;
}
调用时,相当于执行:
A aa;//注意此处只是一个伪代码,并不调用真正的构造函数。
pplus(aa);
五、相关思考
在书中我了解到NRV优化必须要有拷贝构造函数的存在(不管是隐式还是显示),因此下面进行相关试验。
当把拷贝构造函数直接删去时,运行结果并没有发生变化。因此我们 可以知道隐式的拷贝构造函数(不管是bitwise copy还是合成的copy constructor)依然会触发NRV优化。
当把拷贝构造函数置为删除时,即:
A(const A&a)=delete;
此时,会报错:
也就是说触发NRV优化的关键是要有拷贝构造函数,不管是显示定义,还是隐式的,但是不能定义为删除的。
六、没有NRV优化时的情况
以vs自带的编译器为例,我们运行一开始的代码,会得到如下结果:
可以看到和GCC的编译器是有差别的,此时显然是没有NRV优化的,后来了解到一般编译器在执行形如下面的代码时,会进行一些相应的转化。
B pplus()
{
B f;
return f;
}
此时编译器会做出相应的转化:
void B pplus(B& _result)
{
B f;//预留f的内存空间,此时不会执行构造函数
f.B::B();//调用默认构造函数
_result.B(f);
return;
}
函数调用时(B b=pplus();),编译器会做如下处理:
B b;// 这里只是预留内存,并未调用初始化函数
pplus(b);
所以结合上述的理论,我们可以看到调用了构造函数,拷贝构造函数,2次析构函数。那么我们也可以得到在该种编译器中,拷贝构造函数也是不可或缺的(不管是显式的,还是隐式的),将拷贝构造函数定义为删除的,会引发程序的报错
总结
从上述的分析中,我们可以看到对于以下:
B pplus()
{
B f;
return f;
}
这种返回对象的函数来说,拷贝构造函数(无论是显式的还是隐式的)是不可或缺的,不可以把拷贝构造函数定义为删除的。