为什么在返回临时(rvalue)时在移动构造函数之前调用析构函数

时间:2013-02-04 14:17:02

标签: c++ move-semantics temporary

我怀疑我对移动语义一无所知。鉴于以下代码,我希望调试器(MSVC2010SP1)按以下顺序调用Proxy的成员:

  • Proxy(Resource*)getProxy
  • 中构建临时文件
  • Proxy(Proxy&& other)移动构建p
  • ~Proxy()通过移动
  • 破坏临时的空壳
  • ~Proxy() p超出范围

    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
    

相反,订单是:

  • Proxy(Proxy*)
  • ~Proxy() //这已经比预期更早地调用了close()
  • Proxy(Proxy&& other) //在销毁后移动,p.pResource_值为nullptr
  • ~Proxy() // p超出范围

这对我没有意义。我要做的是跟踪代理类的生命周期,将通过移动构造函数关闭资源的工作从一个对象传递到另一个对象。

2 个答案:

答案 0 :(得分:5)

getProxy()返回对临时的引用,该引用在函数结束时超出范围并导致悬空引用。

答案 1 :(得分:2)

通过右值引用返回实际上并不会导致任何内容被移动。它只是通过引用返回。但是,它与返回左值引用不同,因为调用返回右值引用的函数的表达式是xvalue(而不是左值)。然后可以移动xvalue(作为rvalue表达式的子集)。如果您想从返回左值引用的函数的返回对象移动,则必须使用std::move使其成为右值。

您很少想要实际返回右值引用。它唯一模糊的用途是允许移动对象的私有成员。如果您希望在从函数返回对象时移动该对象,只需按值返回它即可。在您的情况下,如果getProxy的返回类型只是Proxy,则临时对象将被移动到返回的对象中,然后将其移至p(除了任何省略) )。

如果你拥有它,你的临时对象(由Proxy(this)构造)在return语句的末尾被销毁 - 这是析构函数的第一次调用。返回的引用现在引用了一个无效对象,并且通过从此无效引用移动来构造p。这会给你未定义的行为。