Why is the destructor called before the move constructor when returning a temporary (rvalue)

StackOverflow https://stackoverflow.com/questions/14688598

  •  06-03-2022
  •  | 
  •  

Domanda

I suspect I don't understand something about move semantics. Given the following code I would expect the debugger (MSVC2010SP1) to call Proxy's members in the following order:

  • Proxy(Resource*) constructing the temporary in getProxy
  • Proxy(Proxy&& other) move constructing p
  • ~Proxy() destructing the empty shell of the temporary that got its guts taken by move
  • ~Proxy() p goes out of scope

    class Resource
    {
        void open(){}
    public:
        void close(){}
        Proxy && getProxy();
    };
    class Proxy
    {
        Resource *pResource_;
        Proxy(const Proxy& other); //disabled
        Proxy& operator=(const Proxy& other); //disabled
    public:
        Proxy(Resource *pResource):pResource_(pResource){}
        Proxy(Proxy&& other):pResource_(other.pResource_){other.pResource_ = nullptr;}
        ~Proxy()
        {
            if(pResource_)
                pResource_->close();
            pResource_ = nullptr;
        }
    };
    
    Proxy && Resource::getProxy()
    {
            open();
            return Proxy(this);
    }
    
    //somewhere else, lets say in main()
    Resource r;
    {
        auto p = r.getProxy(); 
    }   // p goes out of scope
    

Instead the order is:

  • Proxy(Proxy*)
  • ~Proxy() //this already calls close() earlier than expected
  • Proxy(Proxy&& other) //moving after destruction gives p.pResource_ a value of nullptr
  • ~Proxy() //p goes out of scope

This makes no sense to me. What I'm trying to do is track the lifetime of the proxy class passing the job of closing the resource via the move constructor from one object to another.

È stato utile?

Soluzione

getProxy() returns a reference to a temporary, which goes out of scope at function end and results in a dangling reference.

Altri suggerimenti

Returning by rvalue reference doesn't actually cause anything to be moved. It just returns by reference. However, it's different to returning an lvalue reference because an expression calling a function that returns an rvalue reference is an xvalue (as opposed to an lvalue). The xvalue (as a subset of rvalue expressions) can then be moved from. If you wanted to move from the returned object of a function returning lvalue reference, you would have to use std::move to make it an rvalue.

You very rarely will want to actually return an rvalue reference. The only vaguely common use for it is to allow a private member of an object to be moved from. If you want an object to be moved when you return it from a function, just return it by value. In your case, if the return type of getProxy was just Proxy, the temporary would be moved from into the returned object and then that would be moved from into p (save for any elision).

As you have it, your temporary object (contructed by Proxy(this)) is destroyed at the end of the return statement - this is the first call of the destructor. The returned reference is now referencing an invalid object and p is constructed by moving from this invalid reference. That gives you undefined behaviour.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top