临时对象与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     //...直接处理 __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:


 

时间: 2024-08-28 14:37:18

临时对象与NRV技术的相关文章

c++ 临时对象

我们知道在C++的创建对象是一个费时,费空间的一个操作.有些固然是必不可少,但还有一些对象却在我们不知道的情况下被创建了.通常以下三种情况会产生临时对象: 1,以值的方式给函数传参: 2,类型转换: 3,函数需要返回一个对象时: 现在我们依次看这三种情况: 一,以值的方式给函数传参. 我们知道给函数传参有两种方式.1,按值传递:2,按引用传递.按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本的,也正是因为这个原因,在函数体里对该副本进行任何操

C++标准的规定:非常量的引用不能指向临时对象(转载)

C++标准的规定:非常量的引用不能指向临时对象: 为了防止给常量或临时变量(只有瞬间的生命周期)赋值(易产生bug),只许使用const引用之. 下面的内容转自: http://blog.csdn.net/liuxialong/article/details/6539717 概括一下: 不能把临时对象作为实参传给非const引用. 例如: void conv(string &str) { } int main() { conv("dasd"); // 这里错了,编译器自动生成一

深度剖析C++对象池自动回收技术实现

http://www.tuicool.com/articles/mQBfQfN 对象池可以显著提高性能,如果一个对象的创建非常耗时或非常昂贵,频繁去创建的话会非常低效.对象池通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用.这种方式避免了重复创建耗时或耗资源的大对象,大幅提高了程序性能.本文将探讨对象池的技术特性以及源码实现. 对象池类图 ObjectPool:管理对象实例的pool

一个函数返回临时对象引起的编译器优化问题

我们都知道,如果在一个函数调用另一个函数,假设是 main 函数调用 fun 函数,这个 fun 函数返回一个临时类类型变量,那么这个时候编译器就会在 main 函数申请一个空间并生成一个临时对象,通过拷贝构造函数将 fun 返回的临时变量的值拷贝到这个临时对象.我们看如下的代码: #include <iostream> #include <cstring> using namespace std; class Matrix { public: explicit Matrix(do

C++临时对象减少的方法

C++临时对象产生的场景: 1. 值传递   2. 函数返回   3. 后置++ 等 减少临时对象产生的方法: 1. 使用引用或指针传递 2. 避免隐式类型转换 3. 使用 += 代替 + string x = a + b; // 这里会产生保存a+b值的临时对象 string x(a); x += b; // 这样就不会产生临时对象 4. 使用前置++代替后置++ 前置++的定义: type operator++(); 后置++的定义: const type operator++(int);

对象追踪、临时对象追踪、绝对坐标与相对坐标

1.对象追踪 快捷键SE 若所画的图形不是在水平或垂直方向上的则按极坐标的方法输入 如下图 临时对象追踪 快捷键  键盘上的ctrl+鼠标右键 与对象追踪差不多只是使用时需要点击起点接下来的步骤与使用对象追踪时的一样 2.绝对坐标 就是以坐标系为原点建立的直角坐标系 输入时按直角坐标系的方法输入(x,y) 如下图 这样输入得到的是一条线段的长度 如果不输入0,0,只输入5,10则就是如下图 相对坐标就是以上一个点为原点建立直角坐标系 输入时在输入的坐标前面加上@然后在输入坐标 当在输入时软件发现

C++临时对象销毁时间

下面这段代码会输出什么? [cpp] view plaincopy const char* p = string("hello temprary string").c_str(); cout << p; 下面这段代码运行之后会怎样? [cpp] view plaincopy #include <vector> class Foo { public: Foo() { _p = new char[32]; } ~Foo() { delete _p; } privat

转:C++中临时对象及返回值优化

http://www.cnblogs.com/xkfz007/articles/2506022.html 什么是临时对象? C++真正的临时对象是不可见的匿名对象,不会出现在你的源码中,但是程序在运行时确实生成了这样的对象. 通常出现在以下两种情况: (1)为了使函数调用成功而进行隐式类型转换的时候. 传递某对象给一个函数,而其类型与函数的形参类型不同时,如果可以通过隐式转化的话可以使函数调用成功,那么此时会通过构造函数生成一个临时对象,当函数返回时临时对象即自动销毁.如下例: //计算字符ch

STL——临时对象的产生与运用

所谓临时对象,就是一种无名对象.它的出现如果不在程序员的预期之下(例如任何pass by value操作都会引发copy操作,于是形成一个临时对象),往往造成效率上的负担.但有时候刻意制造一些临时对象,却又是使程序干净清爽的技巧.刻意制造临时对象的方法是,在型别名称之后直接加一对小括号,并可指定初值,例如Shape(3,5)或int(8),其意义相当于调用相应的constructor且不指定对象名称.STL最常将此技巧应用于仿函数(functor)与算法的搭配上,例如: #include<ios