【问题标题】:How can moved objects be used? [duplicate]如何使用移动的对象? [复制]
【发布时间】:2011-04-21 19:31:05
【问题描述】:

移动一个物体后,它必须是可破坏的:

T obj;
func(std::move(obj));
// don't use obj and let it be destroyed as normal

但是 obj 还能做什么呢?你能把另一个物体移进去吗?

T obj;
func(std::move(obj));
obj = std::move(other);

这是否取决于确切的类型? (例如,std::vector 可以为所有 T 做出您不能依赖的特定保证。)除了对移动对象的破坏之外,所有类型是否都支持某些东西是必需的,甚至是理智的?

【问题讨论】:

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


    【解决方案1】:

    是的,您可以将另一个对象移入其中。 std::swap 会这样做。

    【讨论】:

    • 我忘记了 std::swap,好点。
    • 对于任何类型,除了破坏之外,这似乎是唯一合理的要求。我会注意到移动赋值运算符可以被删除(从而产生编译时错误)并且交换需要 MoveAssignable (§20.3.2p1, 0x FCD) 才能使这项工作 - 暗示某些类型,这不能与交换一起使用,不是 MoveAssignable。
    • 它似乎 has_move_assign (§20.7.4.3, table 45, 0x FCD) 告诉您是否满足 MoveAssignable。
    • @Roger Concepts 已从标准中删除。
    • @Let:引用的部分不是概念提案的一部分,FCD 是在概念被删除约 9 个月后发布的。正如您在另一条评论中所说,“您可能应该阅读一些关于 [这些] 的内容,您似乎不知道它们是什么。”
    【解决方案2】:

    当前的 C++0x 草案要求可以销毁或分配移出的对象。如果您将对象传递给标准库中的函数,那么这就是假设。

    通常认为良好的做法是确保移出对象是满足所有不变量的类型的“工作”对象。但是,它处于未指定状态——如果是容器,你不知道它有多少元素,或者它们是什么,但你应该可以调用size()empty(),并查询它。

    目前的草案还不清楚标准库类型本身的要求,C++ 委员会对此进行了积极的讨论。

    【讨论】:

    • 允许用户调用 size() 方法可能是个好主意,但是如果我编写自己的容器,如果 obj 已被移动,我是否允许将 obj.size() 设为 UB ?
    • 是的,你可以用你自己的类型做你喜欢的事。如果您这样做,我强烈建议您添加一个valid() 查询,因为无法检测到使用时“爆炸”的状态确实很难处理。
    • 我不会说每个启用移动的课程都必须支持作业。但是,如果该类确实支持某种类型的分配,那么这个/这些应该仍然可以在“移出”状态下使用。
    • 对于容器,David Abrahams 至少建议 move-assigned-to 容器应该以某种方式破坏其原始元素。考虑通过交换实现移动分配的 vector。这不是一个好主意,因为旧的流对象可能仍然存在于另一个向量中,而不是被关闭。
    • C++0x 库要求启用移动的类支持对已移动对象的赋值,因此如果您有 std::vector<MyClass>,那么 MyClass 最好支持移动后的赋值。如果您不将您的类与标准库一起使用,那么您可以做自己喜欢的事情。
    【解决方案3】:

    这就是类型语义。你决定。如何实施这一举措取决于您。

    一般情况下,状态应该与使用非参数构造函数获得的状态相同。

    顺便说一句。 move 仅在将数据块存储在指针(或其他可移动类)后面时才有意义。

    【讨论】:

    • 我问的是这些类型的用户,而不是实现者。 “如果我在 T 上编写一个模板化的函数,我知道哪些操作适用于任何 T?” T 可以是任何东西,从 int 到 std::vector 再到 anything else.
    • @Roger:如果你写一个函数模板,那么你对可以传递的类型有要求。如果类型不符合要求,则错误使用。
    • @Let:这就是为什么我要问任何类型都保证了哪些要求。例如,请参阅指出移出对象可以移入(“move-assigned”)的答案。
    • @Roger 好吧,在这种情况下它超级简单。绝对没有保证。
    • @Let: 已经有一个保证指出:你可以move-assign进去。
    【解决方案4】:

    这取决于类代码。如果类没有右值引用构造函数和赋值运算符,则忽略 std::move。 std::move 不会移动任何东西,它只是允许将其参数视为右值引用,如果有适当的函数可用。

    正确编写的 && 构造函数和 operator= 必须使参数实例处于某种一致的状态,例如空字符串,并且对象应该可用。如果有operator=,则可以将另一个对象正确分配给这样的空实例。

    编辑。

    一般来说,std::move 应该用于将移动语义应用于不是右值的变量,但实际上它是:

    SomeClass::SomeClass(SomeClass&& v) { // 在这个函数内部,v 不再是右值。但我知道实际上 // 这是右值,并使用 std::move 其他功能(标准::移动(v)); }

    在这种情况下,对 v 的最低要求是它应该能够毫无问题地死掉。

    当 std::move 用于实际上不是右值引用的变量时,实际上,这个变量的可用性可能是未定义的。对于我自己的课程,我会确保这种情况的某种一致性。对于另一个类 - 它取决于特定的类实现,但我不会将 std::move 应用于实际上不是右值引用的对象。我真的不知道标准中是如何定义的(以及是否定义)。

    【讨论】:

    • 我在问这种一致状态的确切要求是什么。我目前理解事物的方式,在 func(std::move(obj)); 之后,你不能保证 obj.method() 可以被调用,除非你确切地知道 T 是什么并且知道它保证没问题。
    • @Roger 当然可以调用obj.method(); 对象不会去任何地方。它只是改变了状态。
    • @Let:“不能保证 obj.method() 可以被调用”,我的意思是“不能保证 obj.method() 不是 UB。”。我意识到语法格式正确。
    • @Roger 我不知道 UB 是什么,但无论如何,如果移动运算符使类进入无法从公共接口调用方法的状态,它只是没有正确实现。
    • @Roger 您正在编写移动代码,您有责任保持对象处于可用状态。哪些资源被“窃取”的对象应该表现为默认的空对象。如果这是不可能的,对象方法必须抛出一些已知且记录在案的异常或返回失败(如 .NET 中的 ObjectDisposedException)。这就是我会用 && 运算符编写课程的方法。但是,我不知道标准是否需要这个。
    【解决方案5】:

    我终于从 cmets 中了解到。你应该检查一下:http://www.boost.org/doc/libs/1_44_0/libs/concept_check/concept_check.htm

    这将允许您检查作为概念模板参数提供的类型(类型的特征)。我不确定他们是否已经有一个可移动的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-18
      相关资源
      最近更新 更多