c++重载的+运算符没有返回一个临时对象,原因是什么

c++ overloaded + operator does not return a temporary object , why?

本文关键字:一个 临时对象 是什么 重载 运算符 返回 c++      更新时间:2023-10-16

这是源代码:

#include<iostream>
using namespace std;
class cat{
    private:
      int a;
    public:
      cat():a(1){
         cout << "const : " << this << endl;
      }
     ~cat(){
         cout << "dest :  " << this << endl;
      }
    cat operator+(cat& rhs){
         cout << "+" << endl;
         cat x;
         x.a=a+rhs.a;
         return x;
      }
    cat operator=(const cat& rhs){
        cout << "= :  " <<this << endl;
        a=rhs.a;
        return (*this);
      }
    cat(const cat& rhs){
        cout << "copy const : " << this << endl;
        a=rhs.a;
      }
 };
 int main(){
 cat ob1;
 cat ob2;
 cat ob3;
 ob1=ob2;
 cout << "n 1----1 n" << endl;
 ob3=(ob1+ob2);
 cout << "n 2----2 n" << endl;
 cat ob4=ob1+ob2;
 cout << "n 3----3 n" << endl;
 }

这是输出:

const : 0x22ff20          // ob1 created
const : 0x22ff1c          // ob2 created
const : 0x22ff18          // ob3 created
= : 0x22ff20             // calling = operator 
copy const  :  0x22ff24   // return temporary object using copy constructor
dest :  0x22ff24          // temporary object is destroyed
  1 ---- 1
+                     // operator + is called
= : 0x22ff2c          // it jums to = operator #### (why ?) ####
copy const : 0x22ff28  // = create a temporary object 
dest :   0x22ff28      // temporary object created by = is destroyed 
dest :   0x22ff2c      // x inside + operator is destroyed
                       // ##################################################
                       // #### HERE #### copy constructor to create a temporory object 
                       // like what happend in = operator and also destructor of this 
                       // temporary object did not called
                       // ##################################################
  2 ---- 2      
+                     // here + operator is called 
const :  0x22ff14     // x is creted 
                      //######################""
                      //#### HERE #### copy constructor ob4 that take ob1+ob2 as an
                      // argument did not get called, why ?
                      // and also + operator did not return a temporary object and then
                      // use it as an argument for the copy constructor
                      //#######################
  3 ---- 3 
dest :  0x22ff14        // x   destroyed
dest :  0x22ff18        // ob3 destroyed
dest :  0x22ff1c        // ob2 destroyed
dest :  0x22ff20        // ob1 destroyed

问题开始于1和2之间,也开始于2和3之间。

所以我的问题在输出中
在1和2之间:为什么+操作符没有返回一个teporary对象,然后像在=操作符中发生的那样销毁它

介于2和3之间:为什么+运算符没有返回一个临时对象,该对象将用作复制构造函数中的参数以创建ob4?

我知道这很长时间,但我真的很感激你的帮助。

允许编译器移除(elide)副本构造并就地构建(在最终目的地)。因此,它是完全有效的。

PS:这里的小错误:

cat operator=(const cat& rhs){

应为:

cat &   operator=(const cat& rhs){
// ^^^

有了这个修正,我得到了:

 1----1 
+                       // + called.
const : 0x7fff6b29e848  // Local object to + constructed.
                        // But the return value will be used as a const ref parameter
                        // to the assignment operator. So we can elide the actual copy
                        // if we create the temporary object at the destination and use that.
= :  0x7fff6b29e830     // Now we are in the assignment.
                        // Just copy the value from the temporary object we created as part
                        // of the optimizations.
dest :  0x7fff6b29e848  // All finished destroy the temporary.
                        // Note: I use the term temporary very loosely.
                        //       And refer you to the as-is rule.

所以你的问题似乎是:

在+方法中,您创建了一个名为"x"的本地cat,并返回它。您希望有一个为此调用的构造函数,然后是一个创建匿名返回值的复制构造函数,再是一个x的析构函数,最后是一个返回值的析构函数。你想知道为什么没有发生这种事。(如果我错了,请纠正我)

简而言之,这个问题的答案几乎可以肯定是编译器优化。编译器看到你只是创建x,更改它的一个成员,然后返回它,所以它去掉了所有这些,只是在原地构造返回值。

编译器已经消除了从内部变量x到返回值的副本,这是通过将两个对象放在同一内存位置并延长对象的生存期来完成的。临时的生存期将一直延长到完整表达式结束,直到您确定为x的销毁点为止。我手头没有编译器,但我敢打赌,如果您在operator+中打印x的地址,并将参数的地址打印到operator=,它们将是相同的。

至于优化是如何实现的,在我所知道的所有编译器中,按值返回的函数的调用约定规定调用方为返回值保留空间,并将指向未初始化内存的隐藏指针传递给函数。函数中的return语句将在该内存块上创建返回的对象。在这种情况下,在处理operator+时,编译器意识到x的全部目的是作为构造返回语句的蓝图,并决定x放置在返回值的地址传递中,避免创建两个单独的对象和复制。

进一步阅读:值语义:NRVO//值语义:复制省略

EDIT:NRVO在运算符+中的等价代码转换

调用约定使operator+等效于:

void operator+( void* rtn, cat * const this, cat const &rhs ) {
   cout << "+" << endl;
   cat x;
   x.a=this->a+rhs.a;
   new (rtn) cat(x);    // return x;
} 

现在编译器看到它可以很容易地执行NRVO,并将其进一步转换为:

void operator+( void* rtn, cat * const this, cat const &rhs ) {
   cout << "+" << endl;
   new (rtn) cat;                                 // cat x;
   *static_cast<cat*>(rtn).a = this->a+rhs.a;     // `x` is an alias to 
   // return x; unnecessary, the return object has been built in place already
}