Tour

Action speaks louder than words ...

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  《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 }
View Code

程序中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 }
View Code

程序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 }
View Code

返回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 }
View Code

程序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 }
View Code

这里编译器以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 }
View Code

上面的代码在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 }
View Code

Debug/Release选项下输出的结果不同:

Debug:

Release:


 

posted on 2014-10-20 11:36  Tourun  阅读(496)  评论(0编辑  收藏  举报