我对使用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!
请注意,析构函数只调用一次。这表明我的理解是关闭的。为什么析构函数不会被调用两次?以下是我对 应该发生的事情的解释:
Foo f1
。Foo f1
传递给work
,其中
采用右值f2
。Foo
的移动构造函数是
调用,移动f1
中的所有资源
到f2
。f2
的析构函数,
释放所有资源。f1
的析构函数,
实际上并没有做任何事情
因为所有资源都已转移
到f2
。还是,析构函数是
尽管如此。但由于只调用了一个析构函数,因此不会发生第4步或第5步。我从析构函数中做了一个回溯,看看它是从哪里调用的,它是从第5步调用的。那么为什么f2
的析构函数也没被调用呢?
编辑:好的,我对此进行了修改,以便实际管理资源。 (一个内部存储器缓冲区。)但是,我得到的结果是析构函数只被调用一次。
答案 0 :(得分:9)
修改 (新的正确答案)
对不起,仔细看看代码,似乎答案更简单:
你永远不会调用移动构造函数。你永远不会真正移动对象。您只需将一个右值引用传递给work
函数,该函数在该引用上调用成员函数,该函数仍然指向原始对象。
原始答案,为后代保存
为了实际执行移动,您必须在Foo f3(std::move(f2));
内部使用work
。然后,您可以在f3
上调用您的成员函数,这是一个新对象,通过从f
移动而创建
据我所知,你根本没有移动语义。你只是看到普通的旧版本。
要进行移动,您必须使用std::move
(或者特别是传递给构造函数的参数必须是未命名/临时的)右值引用,例如从{{返回的引用1}})。否则它被视为一个普通的老式左值引用,然后副本应该发生,但像往常一样,允许编译器优化它,留下你正在构建一个对象,并且物体被摧毁。
无论如何,即使使用移动语义,也没有理由为什么编译器不应该做同样的事情:只是优化移动,就像它已经优化了副本一样。移动很便宜,但只需构建一个你需要它的对象,而不是构造一个,然后将它移动到另一个位置并在第一个位置调用析构函数,它仍然更便宜。
值得注意的是,你使用的是相对较旧的编译器,早期版本的规范还不清楚这些“僵尸对象”会发生什么。所以GCC 4.3可能不会调用析构函数。我相信它只是最后一个版本,或者也许是它之前的版本,明确要求析构函数被调用
答案 1 :(得分:2)
请注意,编译器可以优化不需要的构造/销毁,实际上只存在一个对象。对于右值引用(特别是为此目的而发明),尤其如此。
我认为你对所发生的事情的解释是错误的。不调用移动构造函数:如果该值不是临时值,则右值引用的行为与普通引用相同。
也许this article会带来更多关于右值参考语义的信息。