http://blog.chinaunix.net/uid-25872711-id-3022578.html
Efficent C ++ 第五章 临时对象
临时对象
编译器会产生临时对象,我们需要了解产生临时对象的情况,以完成高效的编码。
对象定义
class Rational { friend Rational operator+(const Rational&, const Rational&); public: Rational (int a = 0, int b = 1 ) : m(a), n(b) {} private: int m; // Numerator int n; // Denominator };
可以用以下几种方式实例化Rational:
Rational r1(100); // 1 Rational r2 = Rational(100); // 2 Rational r3 = 100; // 3
第一种情况,可以保证编译器不会产生临时对象,其它都会产生临时对象。但是实际应用中,大部分编译器会优化掉临时对象,这三种形式的初始化是等价的。
类型不匹配
上面的第3种初始形式,就是类型不匹配的情况。这种情况下编译器会实现类型的转换,编译器会查找构造函数如果存在类型匹配的构造函数则可以转换成功,否则编译出错。此时构造函数做为类型转换构造函数。
整个过程可用如下伪码表示:
Rational _temp; // Place holder for temporary _temp.Rational::Rational(100,1); // Construct temporary r.Rational::operator=(_temp); // Assign temporary to r _temp.Rational::~Rational(); // Destroy the temporary
可以禁止使用这种转换,把构造函数声明为explicit,如:
class Rational { public: explicit Rational (int a = 0, int b = 1 ) : m(a), n(b) {} ... };
可以重载operator =操作符,消除临时对象,如:
class Rational { public: ... // as before Rational& operator=(int a) {m=a; n=1; return *this; } };
如下情况:
Complex a, b; ... for (int i; i < 100; i++) { a = i*b + 1.0; }
由于类型不匹配,1.0会产生一个临时对象,并且它是保持不变的。这样的写法,每一次循环都会对这个临时对象做构造和析构,这是一个性能损失。可修改如下:
Complex one(1.0); for (int i = 0; i < 100; i++) { a = i*b + one; }
这样,只需要对1.0做一个构造和析构即可完成同样的操作。
按值传递
很显然这会产生临时对象,函数每一个参数都是在函数活动记录中的一个临时对象。参数实例化过程就是临时对象做拷贝构造的过程,因此可能的情况下尽少使用按值传递。
T _temp; _temp.T::T(t); // copy construct _temp from t g(_temp); // pass _temp by reference _temp.T::~T(); // Destroy _temp
按值返回
以operator +操作为例:
string operator+ (const string& s, const string& p) { char *buffer = new char[s.length() + p.length() + 1]; strcpy(buffer,s.str); // Copy first character string strcat(buffer,p.str); // Add second character string string result(buffer); // Create return object delete buffer; return result; }
有如下典型调用:
string s1 = "Hello"; string s2 = "World"; string s3; s3 = s1 + s2; // s3 <- "HelloWorld"
其中s3 = s1 + s2,语句会产生如下调用:
1)operator+(const string &, const string &);由s1+s2调用。
2)string::string(const char *);函数内部由string result(buffer);调用。
3)string::~string() ;函数调用结束前,析构result
4)string::operator=(const string &);拷贝构造临时对象
5)string::~string();析构临时对象
可以用第4章中介绍的RVO返回值优化,消除result对象的开销。现在还存在一个临时对象,它为什么会产生?
因为我们不能修改s3中的旧内容,用s1+s2产生的结果覆盖它,由此编译器不允许跳过string::operator = ()操作符,来把s3中的旧内容替换掉,那么临时对象就是必须的。但是如果s3本来就是没有旧内容的新string对象呢?这样编译器就可以直接使用s3,把s1+s2的结果直接拷贝构造到s3,这样s3取代了临时对象。如下形式即可:
{ string s1 = "Hello"; string s2 = "World"; string s3 = s1 + s2; // No temporary here. ... }
使用op=()消除临时对象
同样s3 = s1 + s2的情况,可以使用string operator += ()完成:
s3 = s1; // operator=(). No temporary. s3 += s2; // operator+=(). No temporary.
同样可以做到不产生临时对象的高效。
s5 = s1 + s2 + s3 + s4; // Three temporaries generated.
上面代码会产生3个临时对象
s5 = s1; s5 += s2; s5 += s3; s5 += s4;
这将没有临时对象产生,虽然不太美观,但是这是高效的。