首页 > 代码库 > 临时对象与NRV技术
临时对象与NRV技术
《More Effective C++》中讲到,在C++中真正的临时对象是看不见的,它们不出现在你的源代码中。建立一个没有命名的非堆(non-heap)对象会产生临时对象,这种未命名的对象通常在两种条件下产生:为了使函数成功调用而进行隐式类型转换和函数返回对象时。
1 size_t countChar(const string& str, char ch);2 3 int main()4 { 5 char c; 6 cin >> c >> setw(MAX_STRING_LEN) >> buffer; 7 cout << "There are " << countChar(buffer, c)<< " occurrences of the character " << c<< " in " << buffer << endl;8 }
程序中countChar 第一个入参类型是 const string&,而调用是传入的是buffer[],一个字符数组,此时编译器会通过以buffer做为参数调用string的构造函数来初始化这个临时对象。countChar的参数str被绑定在这个临时的string对象上。当countChar返回时,临时对象自动释放。这样的类型转换很方便,但是从效率的观点来看,临时string对象的构造和释放是不必要的开销。仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。
1 void uppercasify(string& str);2 3 int main()4 {5 char subtleBookPlug[] = "Effective C++"; 6 uppercasify(subtleBookPlug); // 错误!7 }
程序line7报错,原因是 uppercasify 函数可能对入参进行修改,而实参是 char 数组,所以在调用函数时会创建一个临时对象,而临时对象具有 const 属性,函数的入参是non-const的引用,无法进行赋值,所以C++语言禁止为非常量引用(reference-to-non-const)产生临时对象。
建立临时对象的第二种环境是函数返回对象时:const Number operator+(const Number& lhs, const Number& rhs); 一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数,这种调用是不能避免的。以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的。这种技巧是返回constructor argument而不是直接返回对象。
1 const Rational operator*(const Rational& lhs, const Rational& rhs) 2 { 3 return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); 4 }
返回constructor argument而不出现局部对象,C++规则允许编译器优化不出现的临时对象(temporary objects out of existence)。
1 int main()2 {3 Rational a = 10; 4 Rational b(1, 2); 5 Rational c = a * b;6 7 return 0;8 }
程序line6,编译器就会被允许消除在operator*内的临时变量和operator*返回的临时变量。它们能在为目标c分配的内存里构造return表达式定义的对象。如果你的编译器这样去做,调用operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数――建立c时调用的构造函数。而且你不能比这做得更好了,因为c是命名对象,命名对象不能被消除。
函数返回对象时,临时对象的创建和销毁是不必要的开销,那么可以使用NRV技术来减少这些不必要的开销,提高程序的效率。Named Return Value Optimization具名返回值优化,是编译器的一项优化操作,被视为标准C++便一起的一个义不容辞的优化操作,引用《深度探索C++对象模型》中的例子:
1 X bar() 2 { 3 X xx; 4 //...处理xx 5 return xx; 6 } 7 //编译器把其中的xx以__result取代: 8 void bar(X &__result) 9 {10 //default constructor 调用11 //c++伪代码12 __result.X::X();13 14 //...直接处理 __result15 16 return;17 }
这里编译器以result参数取代了named return value,返回时不会调用copy constructor拷贝。更具体的描述如下:
A a = f();
此处f()函数创建了一个A类对象b,然后返回对象b,在对象b返回时,一般要调用拷贝构造函数,把函数f()里边的局部对象b拷贝到函数外部的对象a,但如果用了NRV优化,那么就不必调用拷贝构造函数,编译器可以这么做:把a的地址传递给f(),不让f()创建返回的对象b,用a代替b的地址,这样当要返回对象b的时候,就不必拷贝了,因为b就是a。
《深度探索C++对象模型》中提到要激活NRV,必须提供copy construct函数,下面是VS2010中的测试:
1 using namespace std; 2 class Complex 3 { 4 friend Complex operator+(const Complex&, const Complex&); 5 public: 6 Complex(double r=0.0, double i=0.0):real(r),imag(i) 7 { 8 cout<<"Complex real="<<r<<", imag="<<i<<endl; 9 }10 Complex(const Complex& c):real(c.real), imag(c.imag)11 {12 cout<<"Complex(const Complex& c), real="<<real<<", imag="<<imag<<endl;13 }14 Complex& operator=(const Complex& c)15 {16 cout<<"Complex& operator=, real="<<c.real<<", imag="<<c.imag<<endl;17 if(this == &c)18 return *this;19 real = c.real;20 imag = c.imag;21 }22 ~Complex()23 {24 cout<<"~Complex(), real="<<real<<", imag="<<imag<<endl;25 }26 private:27 double real;28 double imag;29 };30 Complex operator+(const Complex& a, const Complex& b)31 {32 // Complex retVal(a.real + b.real, a.imag + b.imag);33 // return retVal;34 return Complex (a.real + b.real, a.imag + b.imag);35 }36 37 int _tmain(int argc, _TCHAR* argv[])38 {39 Complex a(12, 23), b(23, 34);40 Complex c = a+b;41 42 cout<<"################################"<<endl;43 44 return 0;45 }
上面的代码在Debug/Release选项下结果相同:
?
而如果将 Complex operator+ 函数修改:
1 Complex operator+(const Complex& a, const Complex& b)2 {3 Complex retVal(a.real + b.real, a.imag + b.imag);4 return retVal;5 }
Debug/Release选项下输出的结果不同:
Debug:
Release:
临时对象与NRV技术