什么构成C ++ 11中的“从…移到”对象的有效状态?

我一直在试图围绕C ++ 11中的移动语义如何工作,而且我在理解移动对象需要满足的条件方面遇到了很多麻烦。 看看这里的答案并不能真正解决我的问题,因为看不到我们的问题,因为我们无法看出如何以合理的方式将它应用于pimpl对象,尽pipe移动语义对于pimpls来说是完美的 。

我的问题的最简单的例子涉及到pimpl的成语,就像这样:

class Foo { std::unique_ptr<FooImpl> impl_; public: // Inlining FooImpl's constructors for brevity's sake; otherwise it // defeats the point. Foo() : impl_(new FooImpl()) {} Foo(const Foo & rhs) : impl_(new FooImpl(*rhs.impl_)) {} Foo(Foo && rhs) : impl_(std::move(rhs.impl_)) {} Foo & operator=(Foo rhs) { std::swap(impl_, rhs.impl_); return *this; } void do_stuff () { impl_->do_stuff; } }; 

现在,我从Foo搬走后该怎么办? 我可以安全地摧毁移动的物体,我可以指定它,这两个都是至关重要的。 但是,如果我尝试用我的Foo ,它会爆炸。 在我为Foo定义添加移动语义之前,每一个Foo满足它可以做到的不variables,不再是这种情况。 似乎也没有太多好的select,因为(例如)把移动的Foo放到一个新的dynamic分配中,这部分地破坏了移动语义的目的。 我可以检查在do_stuff是否impl_ ,并将它初始化为一个默认的FooImpl如果是的话),但是会增加一个(通常是虚假的)检查,如果我有很多方法,那就意味着要记住每一个检查。

我应该放弃能够do_stuff是一个合理的不变的想法吗?

您可以为types定义和logging什么是“有效”状态,以及可以在types的移动对象上执行什么操作。

移动标准库types的对象会将对象置于未指定状态,可以将其视为正常来确定有效操作。

17.6.5.15库types的移出状态[lib.types.movedfrom]

在C ++标准库中定义的types对象可以从(12.8)中移出。 移动操作可以明确指定或隐式生成。 除非另有规定,否则此类移动物体应置于有效但未指定的状态。

处于“有效”状态的对象意味着标准为该types指定的所有要求仍然成立。 这意味着您可以使用前提条件为true的标准库types的任何操作。

通常情况下,一个对象的状态是已知的,所以你不必检查它是否符合你想要执行的每个操作的先决条件。 移动对象唯一的区别是你不知道状态,所以你必须检查。 例如,在查询string的状态以确定满足pop_back()的前提条件之前,不应该对已移动的string执行pop_back()操作。

 std::string s = "foo"; std::string t(std::move(s)); if (!s.empty()) // empty has no preconditions, so it's safe to call on moved-from objects s.pop_back(); // after verifying that the preconditions are met, pop_back is safe to call on moved-from objects 

状态可能是未指定的,因为为标准库的所有不同实现创build一个有用的要求是非常麻烦的。


既然你不仅负责规范,而且还负责你的types的实现,你可以简单地指定状态,避免查询的需要。 例如,指定从pimpltypes对象移动导致do_stuff成为具有未定义行为的无效操作(通过取消引用空指针)是完全合理的。 该语言的devise使得移动只发生在无法对移出的对象做任何事情的情况下,或者当用户非常明显且非常明确地指示移动操作时发生,因此用户不应该为移动对象感到惊讶,从对象。


另外请注意,标准库定义的“概念”不会为移出对象提供任何限制。 这意味着为了满足标准库定义的任何概念的要求,types的移动对象必须满足概念需求。 这意味着如果你的types的对象没有保持在一个有效的状态(由相关的概念定义),那么你不能使用它与标准库(或结果是未定义的行为)。

但是,如果我尝试用我的Foo做文章,它会爆炸。

是。 那么这样做:

 vector<int> first = {3, 5, 6}; vector<int> second = std::move(first); first.size(); //Value returned is undefined. May be 0, may not 

标准使用的规则是使对象保持有效 (意味着对象工作)但未指定状态。 这意味着您可以调用的唯一函数是那些对对象的当前状态没有任何条件的函数。 对于vector ,您可以使用其复制/移动赋值运算符,以及clearempty以及其他一些操作。 所以你可以这样做:

 vector<int> first = {3, 5, 6}; vector<int> second = std::move(first); first.clear(); //Cause the vector to become empty. first.size(); //Now the value is guaranteed to be 0. 

对于你的情况,复制/移动任务(从任何一方)应该仍然工作,应该析构函数。 但是你的所有其他function都有一个基于未被移出的状态的先决条件。

所以我没有看到你的问题。

如果你想确保没有Pimpl类的实例是空的,那么你将实现适当的复制语义并禁止移动。 运动需要物体处于空闲状态的可能性。