c++ - std::move 之后的僵尸对象

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

我对使用 C++0x move 语义 move 对象之后的状态感到困惑。我的理解是,一旦一个对象被 move ,它仍然是一个有效的对象,但它的内部状态已经改变,因此当它的析构函数被调用时,没有资源被释放。

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

但是,当我执行一个简单的测试时,这并没有发生:

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 的 move 构造函数是 调用, move f1 中的所有资源 到 f2
  4. 现在 f2 的析构函数被调用, 释放所有资源。
  5. 现在 f1 的析构函数被调用, 这实际上并没有做任何事情 因为所有资源都转移了 到 f2。不过,析构函数是 尽管如此。

但由于只调用了一个析构函数,因此第 4 步或第 5 步不会发生。我对析构函数进行了回溯,以查看它是从哪里调用的,它是从第 5 步开始调用的。那么,为什么不调用 f2 的析构函数呢?

编辑: 好吧,我修改了这个,所以它实际上是在管理资源。 (一个内部内存缓冲区。)尽管如此,我还是得到了析构函数只被调用一次的相同行为。

最佳答案

编辑 (新的正确答案)
抱歉,仔细查看代码,似乎答案要简单得多: 你永远不会调用 move 构造函数。你永远不会真正 move 对象。您只需将右值引用传递给 work 函数,该函数在该引用上调用一个成员函数,它仍然指向原始对象。

原始答案,为后人保存

为了实际执行 move ,您必须在 work 中包含类似 Foo f3(std::move(f2)); 的内容。然后你可以在 f3 上调用你的成员函数,这是一个新对象,通过从 f

move 创建

据我所知,您根本没有理解 move 语义。您只是看到了普通的旧拷贝省略。

要进行 move ,您必须使用 std::move(或者具体来说,传递给构造函数的参数必须是未命名/临时的)右值引用,例如从 std::move 返回)。否则,它会被视为普通的老式左值引用,然后 应该 进行复制,但像往常一样,允许编译器优化它,留下一个正在构造的对象,还有一个对象被销毁。

无论如何,即使有 move 语义,编译器也没有理由不做同样的事情:只是优化 move ,就像它已经优化掉拷贝一样。 move 是便宜的,但是只在需要它的地方构造对象,而不是构造一个对象,然后将它 move 到另一个位置并在第一个位置调用析构函数,它仍然更便宜。

同样值得注意的是,您使用的是一个相对较旧的编译器,并且规范的早期版本对于这些“僵尸对象”应该发生什么非常不清楚。所以 GCC 4.3 可能只是不调用析构函数。我相信这只是最后一次修订,或者可能是之前的修订,明确要求调用析构函数

关于c++ - std::move 之后的僵尸对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4082065/

相关文章:

C++:如何返回模板类中定义的别名?

c++ - 类型双关和 union

c++ - 具有 const 数据成员的类的 std::move 和右值赋值运算符

c++ - 这对会被感动吗?

c++ - 线程安全的 unique_ptr move

C++ boost tribool三元运算符错误值

c++ - 用于打包数据的标准方法

c++ - 我应该如何为小于标准的 std::mersenne_twister_engine 选择参数?

c++ - g++:用闭包类型初始化的 std::function 总是使用堆分配?

c++ - 为什么允许 std::atomic_{char,schar,etc.} typedef 是 std::atomic<T> 基类的类型定义,而不仅仅是 atomic<T>?