语义上,函数调用结束,返回值会通过拷贝构造一个临时匿名对象传出来(因为函数体中的都是局部变量,return后的对象调用完成都超过作用域,不存在了)。
先上代码:
#include
using namespace std;
class MyClass
{
private:
int m_i;
public:
MyClass(){ m_i = 0; cout << "this is default constructor!" << endl; } //缺省构造函数
MyClass(const MyClass& that); //拷贝构造函数
MyClass& operator=(const MyClass& that); //赋值构造函数
};
MyClass::MyClass(const MyClass& that)
{
cout << "this is copy constructor!" << endl;
this->m_i = that.m_i;
}
MyClass& MyClass::operator=(const MyClass& that)
{
cout << "this is assignment constructor!" << endl;
if (this != &that)
{
return *this;
}
else
{
this->m_i = that.m_i;
return *this;
}
}
MyClass gfunc()
{
MyClass obj;
return obj;
}
MyClass func()
{
return MyClass();
}
int main()
{
MyClass myObj;
cout << "________" << endl;
myObj = gfunc();
cout << "________" << endl;
MyClass myObj2 = gfunc();
cout << "________" << endl;
MyClass myObj3 = func(); //RVO优化
cout << "________" << endl;
myObj3 = func(); //RVO优化
cin.get();
return 0;
}
在VS2013下,最终的输出结果如下:
myObj = gfunc()这句,一共有3次构造函数的调用。包括gfunc()函数体内部的局部对象obj的构造,return返回值一个匿名的临时对象的构造。gfunc()函数体执行完成,return之后的obj对象已经消亡,为了把返回值传出来,必须借助于这个临时对象。
借助于C++编译器的RVO技术(return value optimization),return 后是调用构造函数:若是用来给对象赋值,则会省掉一次拷贝构造函数的调用(用来传出返回值的匿名临时对象),代码中的 myObj3 = func();若是用来初始化对象,那么还可以省掉一次赋值构造函数的调用,代码中的 MyClass myObj3 = func()。(注意这里要区别C++中的初始化和赋值,初始化是分配空间的同时赋值,一个语句完成;而赋值是先前已经有了空间)。
如果return 之后,是一个具名的对象,编译器可以做NRV优化。此时如果返回值用来初始化对象,可以省掉一次赋值构造函数的调用。 代码中的 MyClass myObj2 = gfunc() ,相当于直接用把return 后的临时对象拷贝构造到myObj2中。
上边两条是编译器默认就提供的,比如在VS下。
如果编译器的优化能力更强,还存在更强的NVR优化技术,能省掉拷贝构造函数的调用。
在gfunc()函数中,内部通过默认构造函数局部变量obj,最后又return这个局部变量obj,对于MyClass myObj2 = gfunc() 语句,完全可以直接将myObj2 代替obj,可以在前边的基础上省掉了拷贝构造函数的调用。myObj = gfunc() 则可以直接将return 后的结果赋值给myobj ,也省掉了拷贝构造函数的调用。
上边的代码在GCC -O优化编译下,输出为:
可以看到如果return 后是一个具名变量,也就是是个左值;NRV优化可以省去拷贝构造一个临时匿名变量。GCC下比较好开启,VS中暂时没找到启用方法。
由于NRV优化的存在,可以return之后直接返回一个具名的对象。万一不支持NRV技术,将会拷贝构造一个匿名的临时对象,而且如果存在移动拷贝构造函数,默认的总是移动拷贝构造函数的效率更好,所以当存在移动拷贝构造函数时,将是移动拷贝构造一个临时匿名对象,虽然此时return 后的是一个具名的左值 result,实际相当于 return move(result).
看这里的栗子
上边这个栗子中,函数的返回值类型不能是指向临时变量的引用(无论左值引用还是右值引用)和指针的原则是不能变的;不过可以将函数的返回值(本身可以是个临时变量),赋值给右值引用或者const 左值引用。注意前后的差别。因为函数调用结束,返回值变不存在,实际调用结束返回值是return 后的值的一个拷贝,return 后的那个在调用结束一定是不存在的。
右值引用和const左值引用,会延长临时变量的生存周期,使得引用变量存在时,临时变量也是存在的。