条款19:了解“临时对象”的来源
1️⃣ 什么是临时对象?
局部变量
- 在函数作用域定义的
temp并非“临时对象”,而是“局部对象”。
1
2
3
4
5
6
7template< class T>
void swap(T& object1, T& object2)
{
T temp = object1;
object1 = object2;
object2 = temp;
}- 在函数作用域定义的
临时对象
- “临时对象”是不可见的——不会在你的源代码中出现。
- 只要产生一个
non-heap object而没有为它命名,便诞生了一个“临时对象”。
2️⃣ 临时对象产生于什么情况?
- 发生隐式类型转换,以求函数调用能够成功。
- 当函数返回对象的时候。
3️⃣ 进一步理解
“为了让函数调用成功”而产生的临时对象
产生时机
- 传递某对象给一个函数,而其类型与它即将绑定上去的参数类型不同。
代码解析
1
2
3
4
5
6
7
8
9
10
11
12//返回 ch 在str中的出现个数。
size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
//读入一个 char 和一个 string,利用 setw 避免
//在读入string时产生缓冲区满溢的情况。
cin >> c >> setw (MAX_STRING_LEN) >> buffer;
cout <<"There are " << countChar (buffer, c)
<<" occurrences of the character " << c
<<" in" << buffer << endl;- 自变量是个
char数组,但是相应的函数参数类型却是const string&。 - 当“类型不吻合”的状态消除,此函数调用才会成功。
- 编译器乐意消除此状态,做法是产生一个类型为
string的临时对象。 - 该对象的初始化方式是:以
buffer作为自变量,调用string constructor。 - 于是
countChar的str参数会被绑定于此string临时对象上。 - 当
countChar返回,此临时对象会被自动销毁。
- 自变量是个
转换发生条件
- 当对象以
by value(传值)方式传递 - 当对象被传递给一个
reference-to-const参数时,这些转换才会发生。 - 当对象被传递给一个
reference-to-non-const参数,并不会发生此类转换。
- 当对象以
当函数返回一个对象的时候
案例引入
1
2const Number operator+(const Number& lhs,
const Number& rhs);解析
- 此函数的返回值是个临时对象,因为它没有名称:它就是函数的返回值。
- 因此,每当调用 operator+,便需要为此对象付出构造和析构成本。
解决措施
- 返回值优化(return value optimization)
4️⃣ 返回值优化
案例引入
1
2
3
4
5
6
7
8
9
10class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
int numerator() const;
int denominator() const;
const Rational operator*(const Rational& lhs,
const Rational& rhs);
};问题
- 它返回两个任意数的乘积,
operator*如何能够在不产生新对象的情况下放置该乘积呢?- 答案是不可能,所以它必须产生一个新对象并将它返回。
- 它返回两个任意数的乘积,
是否可以找出消除“
by value返回方式”的神奇方法?返回指针?
1
2
3
4
5
6
7const Rational* operator*(const Rational& lhs,
const Rational& rhs);
Rational a = 10;
Rational b(1, 2) ;
Rational c = * (a * b) ;- 缺点
- 会发生内存泄露
- 缺点
返回引用?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const Rational& operator*(const Rational& lhs,
const Rational& rhs);
Rational a = 10;
Rational b(1, 2);
Rational c = a * b;
const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
return result;
}- 缺点
- 函数返回一个
reference,指向一个不再存活的对象。 - 明确地说,它返回一个
reference,指向局部对象result。但result在operator*返回时自动被销毁了。
- 函数返回一个
- 缺点
5️⃣ 返回值的正确做法
1 | const Rational operator*(const Rational& lhs, |
- 解析
C++允许编译器将临时对象优化,使它们不存在。
如果这样调用
operator*,编译器得以消除“operator*内的临时对象”及“被operator*返回的临时对象”。1
2
3
4Rational a = 10;
Rational b(1, 2);
Rational c = a * b;编译器可以将
return表达式所定义的对象构造于c的内存内。如果这么做,调用
operator*时的临时对象总成本为 0,也就是说没有任何临时对象需要被产生出来。取而代之的是,你只需付出一个
constructor(用以产生c)的代价。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!
