《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 //...直接处理 __result
15
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:
所有评论(0)