std :: move之后的僵尸对象

时间:2010-11-02 21:01:01

标签: c++ c++11 move-semantics

我对使用C ++ 0x移动语义移动之后的对象的状态感到困惑。我的理解是,一旦一个对象被移动,它仍然是一个有效的对象,但它的内部状态已被改变,以便在调用它的析构函数时,不会释放任何资源。

但是如果我的理解是正确的,那么移动对象的析构函数应该仍然被调用。

但是,当我执行一个简单的测试时,这不会发生:

struct Foo
{
    Foo()  
    {
        s = new char[100]; 
        cout << "Constructor called!" << endl;  
    }

    Foo(Foo&& f) 
    {
        s = f.s;
        f.s = 0;
    }

    ~Foo() 
    { 
        cout << "Destructor called!" << endl;   
        delete[] s; // okay if s is NULL
    }

    void dosomething() { cout << "Doing something..." << endl; }

    char* s;
};

void work(Foo&& f2)
{
    f2.dosomething();
}

int main()
{
    Foo f1;
    work(std::move(f1));
}

此输出:

Constructor called!
Doing something...
Destructor called!

请注意,析构函数只调用一次。这表明我的理解是关闭的。为什么析构函数不会被调用两次?以下是我对 应该发生的事情的解释:

    构建
  1. Foo f1
  2. Foo f1传递给work,其中 采用右值f2
  3. Foo的移动构造函数是 调用,移动f1中的所有资源 到f2
  4. 现在调用f2的析构函数, 释放所有资源。
  5. 现在调用f1的析构函数, 实际上并没有做任何事情 因为所有资源都已转移 到f2。还是,析构函数是 尽管如此。
  6. 但由于只调用了一个析构函数,因此不会发生第4步或第5步。我从析构函数中做了一个回溯,看看它是从哪里调用的,它是从第5步调用的。那么为什么f2的析构函数也没被调用呢?

    编辑:好的,我对此进行了修改,以便实际管理资源。 (一个内部存储器缓冲区。)但是,我得到的结果是析构函数只被调用一次。

2 个答案:

答案 0 :(得分:9)

修改 (新的正确答案)
对不起,仔细看看代码,似乎答案更简单: 你永远不会调用移动构造函数。你永远不会真正移动对象。您只需将一个右值引用传递给work函数,该函数在该引用
上调用成员函数,该函数仍然指向原始对象。

原始答案,为后代保存

为了实际执行移动,您必须在Foo f3(std::move(f2));内部使用work。然后,您可以在f3上调用您的成员函数,这是一个新对象,通过从f移动而创建

据我所知,你根本没有移动语义。你只是看到普通的旧版本。

要进行移动,您必须使用std::move(或者特别是传递给构造函数的参数必须是未命名/临时的)右值引用,例如从{{返回的引用1}})。否则它被视为一个普通的老式左值引用,然后副本应该发生,但像往常一样,允许编译器优化它,留下你正在构建一个对象,并且物体被摧毁。

无论如何,即使使用移动语义,也没有理由为什么编译器不应该做同样的事情:只是优化移动,就像它已经优化了副本一样。移动很便宜,但只需构建一个你需要它的对象,而不是构造一个,然后将它移动到另一个位置并在第一个位置调用析构函数,它仍然更便宜。

值得注意的是,你使用的是相对较旧的编译器,早期版本的规范还不清楚这些“僵尸对象”会发生什么。所以GCC 4.3可能不会调用析构函数。我相信它只是最后一个版本,或者也许是它之前的版本,明确要求析构函数被调用

答案 1 :(得分:2)

请注意,编译器可以优化不需要的构造/销毁,实际上只存在一个对象。对于右值引用(特别是为此目的而发明),尤其如此。

我认为你对所发生的事情的解释是错误的。不调用移动构造函数:如果该值不是临时值,则右值引用的行为与普通引用相同。

也许this article会带来更多关于右值参考语义的信息。