const reference to a temporary object becomes broken after function scope (life time)

后端 未结 2 1167
难免孤独
难免孤独 2020-12-03 16:21

While asking this question, I learned const reference to a temporary object is valid in C++:

int main ()
{
  int a = 21;
  int b = 21;

  //error: invalid in         


        
相关标签:
2条回答
  • 2020-12-03 16:44

    My original example is complex.

    Therefore I post here a simpler example and I provide the corresponding ISO C++ standard paragraph.

    This simpler example is also available on coliru.stacked-crooked.com/

    #include <iostream>
    
    struct A
    {
      A(int i) { std::cout<<"Cstr "<< i<<'\n'; p = new int(i); }
     ~A()      { std::cout<<"Dstr "<<*p<<'\n'; delete p;       }
    
      const A& thiz() const { return *this; }
    
      int *p;
    };
    
    const A& constref( const A& a )
    {
      return a;
    }
    
    int main()
    {
      const A& a4 = A(4);
      const A& a5 = A(5).thiz();
      const A& a6 = constref( A(6) );
    
      std::cout << "a4 = "<< *a4.p <<'\n';
      std::cout << "a5 = "<< *a5.p <<'\n';
      std::cout << "a6 = "<< *a6.p <<'\n';
    }
    

    The output using command line g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out:

    Cstr 4
    Cstr 5
    Dstr 5
    Cstr 6
    Dstr 6
    a4 = 4
    a5 = 0
    a6 = 0
    Dstr 4
    

    As you can see, the temporary objects referenced by a5 and a6 are destructed at the end of functions thiz and constref respectively.

    This is an extract of §12.2 Temporary objects, where the bold part applies in this case:

    The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

    • A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
    • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
    • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
    • A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

    This is a more complete example:

    #include <iostream>
    
    struct A
    {
         A()         { std::cout<<"Cstr 9\n";         p = new int(v = 9);      }
         A(int i)    { std::cout<<"Cstr "<<i<<'\n';   p = new int(v = i);      }
         A(const A&o){ std::cout<<"Copy "<<o.v<<'\n'; p = new int(v = 10+o.v); }
        ~A()         { std::cout<<"Del "<<v<<' '<<*p<<'\n'; *p = 88; delete p; }
    
        const A& thiz() const { return *this; }
    
        int *p;
        int  v;
    };
    
    const A& constref( const A& a )
    {
      return a;
    }
    
    std::ostream& operator<<( std::ostream& os, const A& a )
    {
      os <<"{ *p="<< *a.p <<" , v="<< a.v <<" }\n";
      return os;
    }
    
    int main()
    {
        std::cout << "---const A  a1 = A(1)"                "\n";
                         const A  a1 = A(1);
        std::cout << "---const A  a2 = A(2).thiz()"         "\n";
                         const A  a2 = A(2).thiz();
        std::cout << "---const A  a3 = constref( A(3) )"    "\n";
                         const A  a3 = constref( A(3) );
        std::cout << "---const A& a4 = A(4)"                "\n";
                         const A& a4 = A(4);
        std::cout << "---const A& a5 = A(5).thiz()"         "\n";
                         const A& a5 = A(5).thiz();
        std::cout << "---const A& a6 = constref( A(6) )"    "\n";
                         const A& a6 = constref( A(6) );
    
        std::cout << "a1 = "<< a1;
        std::cout << "a2 = "<< a2;
        std::cout << "a3 = "<< a3;
        std::cout << "a4 = "<< a4;
        std::cout << "a5 = "<< a5;
        std::cout << "a6 = "<< a6;
    }
    

    And the corresponding output using same g++ command line:

    ---const A  a1 = A(1)
    Cstr 1
    ---const A  a2 = A(2).thiz()
    Cstr 2
    Copy 2
    Del 2 2
    ---const A  a3 = constref( A(3) )
    Cstr 3
    Copy 3
    Del 3 3
    ---const A& a4 = A(4)
    Cstr 4
    ---const A& a5 = A(5).thiz()
    Cstr 5
    Del 5 5
    ---const A& a6 = constref( A(6) )
    Cstr 6
    Del 6 6
    a1 = { *p=1 , v=1 }
    a2 = { *p=12 , v=12 }
    a3 = { *p=13 , v=13 }
    a4 = { *p=4 , v=4 }
    a5 = { *p=0 , v=5 }
    a6 = { *p=0 , v=6 }
    Del 4 4
    Del 13 13
    Del 12 12
    Del 1 1
    
    0 讨论(0)
  • 2020-12-03 16:57

    The lifetime-extension of a temporary object can be performed only once, when the temporary object gets bound to the first reference. After that, the knowledge that the reference refers to a temporary object is gone, so further lifetime extensions are not possible.

    The case that is puzzling you

    A const& refnop = A(a).nothing();
    

    is similar to this case:

    A const& foo(A const& bar)
    {
        return bar;
    }
    //...
    A const& broken = foo(A());
    

    In both cases, the temporary gets bound to the function argument (the implicit this for nothing(), bar for foo()) and gets its lifetime 'extended' to the lifetime of the function argument. I put 'extended' in quotes, because the natural lifetime of the temporary is already longer, so no actual extension takes place.

    Because the lifetime extension property is non-transitive, returning a reference (that happens to refer to a temporary object) will not further extend the lifetime of the temporary object, with as result that both refnop and broken end up referring to objects that no longer exist.

    0 讨论(0)
提交回复
热议问题