观察以下代码
#include <iostream>
using namespace std;
class Copyable {
public:
Copyable(){}
Copyable(const Copyable &o) {
cout << "Copied" << endl;
}
};
Copyable ReturnRvalue() {
return Copyable(); //返回一个临时对象
}
void AcceptVal(Copyable a) {
}
void AcceptRef(const Copyable& a) {
}
int main() {
cout << "pass by value: " << endl;
AcceptVal(ReturnRvalue()); // 应该调用两次拷贝构造函数
cout << "pass by reference: " << endl;
AcceptRef(ReturnRvalue()); //应该只调用一次拷贝构造函数
}
AcceptVal(ReturnRvalue())需要调用两次拷贝构造函数,一次在ReturnRvalue()函数中,构造好了Copyable对象,返回的时候会调用拷贝构造函数生成一个临时对象,在调用AcceptVal()时,又会将这个对象拷贝给函数的局部变量a,一共调用了两次拷贝构造函数。而AcceptRef()的不同在于形参是常量左值引用,它能够接收一个右值,而且不需要拷贝。
而实际的结果是,不管哪种方式,一次拷贝构造函数都没有调用!
这是由于编译器默认开启了返回值优化(RVO/NRVO, RVO, Return Value Optimization 返回值优化,或者NRVO, Named Return Value Optimization)。编译器很聪明,发现在ReturnRvalue内部生成了一个对象,返回之后还需要生成一个临时对象调用拷贝构造函数,很麻烦,所以直接优化成了1个对象对象,避免拷贝,而这个临时变量又被赋值给了函数的形参,还是没必要,所以最后这三个变量都用一个变量替代了,不需要调用拷贝构造函数。
虽然各大厂家的编译器都已经都有了这个优化,但是这并不是c++标准规定的,而且不是所有的返回值都能够被优化,C++11之后的语法——右值引用,移动语义可以解决编译器无法解决的问题。