1️⃣ 什么是临时对象?

  1. 局部变量

    • 在函数作用域定义的temp并非“临时对象”,而是“局部对象”。
    1
    2
    3
    4
    5
    6
    7
    template< class T>
    void swap(T& object1, T& object2)
    {
    T temp = object1;
    object1 = object2;
    object2 = temp;
    }
  2. 临时对象

    • 临时对象”是不可见的——不会在你的源代码中出现。
    • 只要产生一个non-heap object而没有为它命名,便诞生了一个“临时对象”。

2️⃣ 临时对象产生于什么情况?

  1. 发生隐式类型转换,以求函数调用能够成功。
  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
    • 于是countCharstr参数会被绑定于此string临时对象上。
    • countChar返回,此临时对象会被自动销毁。
  • 转换发生条件

    • 当对象以by value(传值)方式传递
    • 当对象被传递给一个reference-to-const参数时,这些转换才会发生。
    • 当对象被传递给一个reference-to-non-const参数,并不会发生此类转换。

当函数返回一个对象的时候

  • 案例引入

    1
    2
    const Number operator+(const Number& lhs,
    const Number& rhs);
  • 解析

    • 此函数的返回值是个临时对象,因为它没有名称:它就是函数的返回值。
    • 因此,每当调用 operator+,便需要为此对象付出构造和析构成本。
  • 解决措施

    • 返回值优化(return value optimization)

4️⃣ 返回值优化

  • 案例引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class 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
      7
      const 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
      15
      const 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。但resultoperator*返回时自动被销毁了。

5️⃣ 返回值的正确做法

1
2
3
4
5
6
const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
  • 解析
    • C++允许编译器将临时对象优化,使它们不存在。

    • 如果这样调用operator*,编译器得以消除“operator*内的临时对象”及“被operator*返回的临时对象”。

      1
      2
      3
      4
      Rational a = 10;
      Rational b(1, 2);

      Rational c = a * b;
    • 编译器可以将return表达式所定义的对象构造于c的内存内。

    • 如果这么做,调用operator*时的临时对象总成本为 0,也就是说没有任何临时对象需要被产生出来。

    • 取而代之的是,你只需付出一个constructor(用以产生c)的代价。