僵尸对象后std :: move [英] Zombie objects after std::move

查看:422
本文介绍了僵尸对象后std :: move的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



但是,我执行一个简单的测试:

  struct Foo 
{
Foo()
{
s = new char [100];
cout<< 构造函数叫! << endl
}

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

〜Foo()
{
cout< 析构函数叫! << endl
delete [] s; //好的如果s是NULL
}

void dosomething(){cout< 做某事...< endl }

char * s;
};

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

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

此输出:

 构造函数调用! 
做某事...
析构函数调用!

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


  1. Foo f1 c> c> c>
  2. 其中
    取值为 f2
  3. Foo的移动构造函数
    调用,将 f1
    中的所有资源移动到 f2 。 >
  4. 现在 f2 的析构函数被调用,
    释放所有资源。

  5. code> f1 的析构函数被调用,
    实际上不做任何事情
    ,因为所有资源都被转移
    f2

但是因为只有一个析构函数被调用,所以步骤4或步骤5 isn发生了。我从析构函数中做了一个回溯,看看它从哪里被调用,并且从步骤5被调用。所以为什么 f2 的析构函数也叫? / p>

EDIT:好的,我修改了此项,因此实际上是在管理资源。 (内部缓冲区。)仍然,我得到相同的行为,析构函数只被调用一次。

解决方案

编辑 (新的和正确的答案)

对不起,看看代码,看起来答案更简单:
你从来没有调用移动构造函数。你从来没有移动对象。您只需向 work 函数传递一个右值引用,该函数调用该引用上的成员函数,它仍然指向原始对象。 / p>

为后代保存的原始答案



为了真正执行移动,你必须有 Foo f3(std :: move(f2)); 里面工作。然后你可以在 f3 上调用你的成员函数,这是一个新对象,通过移动 f



就我所见,你根本不会得到移动语义。



要移动,您必须使用 std :: move (具体来说,传递给构造函数的参数必须是未命名的/临时的)rvalue引用,例如从 std :: move 返回的引用) 。否则,它被视为一个普通老式的左值引用,然后复制应该发生,但是像往常一样,编译器允许优化它,让你有一个对象被构造,一个对象被破坏。



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



这也值得注意,你使用一个相对老的编译器,和规范的早期版本是非常不清楚这些僵尸对象应该发生什么。所以有可能GCC 4.3只是不调用析构函数。我相信它只是最后一次修订,或者也许是在它之前,明确要求析构函数


I'm confused about the state of an object after it's been moved using C++0x move semantics. My understanding is that once an object has been moved, it's still a valid object, but its internal state has been altered so that when its destructor is called, no resources are deallocated.

But if my understanding is correct, the destructor of a moved object should still be called.

But, that doesn't happen when I perform a simple test:

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));
}

This output:

Constructor called!
Doing something...
Destructor called!

Notice the destructor is only called once. This shows that my understanding here is off. Why wasn't the destructor called twice? Here's my interpretation of what should have happened:

  1. Foo f1 is constructed.
  2. Foo f1 is passed to work, which takes an rvalue f2.
  3. The move constructor of Foo is called, moving all resources in f1 to f2.
  4. Now f2's destructor is called, releasing all resources.
  5. Now f1's destructor is called, which doesn't actually do anything since all resources were transferred to f2. Still, the destructor is called nonetheless.

But since only one destructor is called, either step 4 or step 5 isn't happening. I did a backtrace from the destructor to see where it was being invoked from, and it's being invoked from step 5. So why isn't f2's destructor also called?

EDIT: Okay, I modified this so it's actually managing a resource. (An internal memory buffer.) Still, I get the same behavior where the destructor is only called once.

解决方案

Edit (New and correct answer)
Sorry, looking closer at the code, it seems the answer is much simpler: you never invoke the move constructor. You never actually move the object. You just pass a rvalue reference to the work function, which calls a member function on that reference, which still points back to the original object.

Original answer, saved for posterity

In order to actually perform the move, you have to have something like Foo f3(std::move(f2)); inside work. Then you can call your member function on f3 which is a new object, created by moving from f

As far as I can see, you don't get move semantics at all. You're just seeing plain old copy elision.

for the move to happen, you have to use std::move (or specifically, the argument being passed to the constructor has to be an unnamed/temporary) rvalue reference, such as the one that is returned from std::move). Otherwise it is treated as a plain old-fashioned lvalue reference, and then a copy should happen, but as usual, the compiler is allowed to optimize it away, leaving you with one object being constructed, and one object being destroyed.

Anyway, even with move semantics, there's no reason why the compiler shouldn't do the same thing: just optimize the move, just like it'd have optimized away the copy. A move is cheap, but it's still cheaper to just construct the object where you need it, rather than constructing one, and then moving it into another location and calling the destructor on the first one.

It's also worth noting that you're using a relatively old compiler, and earlier versions of the spec were very unclear on what should happen with these "zombie objects". So it is possible that GCC 4.3 just doesn't call the destructor. I believe it's only the last revision, or maybe the one before it, that explicitly requires the destructor to be called

这篇关于僵尸对象后std :: move的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆