移动的对象可以用于什么?

8
移动对象后,必须能够销毁它:
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的保证)。是否要求或者说所有类型都支持除销毁以外的移动对象操作是合理的?

5个回答

6

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


1
我忘记了std::swap,好的提醒。 - Roger Pate
似乎这是除了销毁之外,任何类型的唯一合理要求。我注意到移动赋值运算符可以被删除(从而产生编译时错误),并且swap需要MoveAssignable(§20.3.2p1,0x FCD)才能使其工作 - 这意味着某些类型不能与swap一起使用,因为它们不是MoveAssignable。 - Roger Pate
而且,它似乎表明 has_move_assign (§20.7.4.3, 表45, 0xFCD) 告诉您是否满足 MoveAssignable。 - Roger Pate
@Roger 概念已从标准中删除。 - Šimon Tóth
@Let:所引用的章节不是概念提案的一部分,且FCD是在概念被删除约9个月后发布的。正如您在另一条评论中所说,“你应该读一些关于[这些]的东西,你似乎不知道它们是什么。” - Roger Pate
@Roger 我不知道他们这么调整了类型特征。抱歉,应该立即检查草案。 - Šimon Tóth

6
C++0x的当前草案要求可以销毁或分配给移动对象。如果您将对象传递给标准库中的函数,则假定只有这些操作。

通常认为,确保移动后的对象是其类型的“工作”对象并满足所有不变量是一种良好的实践。但是,它处于未指定状态---如果它是容器,则不知道它有多少个元素或者它们是什么,但是您应该能够调用size()empty()来查询它。
当前草案对标准库类型本身的要求不清楚,C++委员会正在积极讨论此问题。

允许用户调用size()方法可能是个好主意,但如果我编写自己的容器,是否可以使obj.size()在obj已被移动时成为未定义行为? - Roger Pate
是的,使用您自己的类型可以做任何您喜欢的事情。但我强烈建议如果您这样做,请添加一个 valid() 查询,因为在使用时无法检测到的状态会带来真正的麻烦。 - Anthony Williams
1
我不会说每个支持移动的类都必须支持赋值。但是,如果该类确实支持某种形式的赋值,那么这些赋值在“移动后”状态下仍应可用。 - sellibitze
关于容器,David Abrahams 至少建议移动赋值给容器应该以某种方式销毁其原始元素。考虑 vector<fstream>,其中移动赋值是通过交换实现的。这不是一个好主意,因为旧的流对象可能仍然存在于其他向量中而没有被关闭。 - sellibitze
C++0x库要求支持移动的类能够对移动后的对象进行赋值,因此如果您有一个std::vector<MyClass>,那么MyClass最好在移动后也能支持赋值。如果您不使用标准库来处理您的类,则可以随意操作。 - Anthony Williams

1

这是类型语义。你决定。如何实现移动取决于你。

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

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


@Let:这就是为什么我问任何类型都保证了哪些要求。例如,可以看到答案指出移动的对象可以被移动到(“移动赋值”)。 - Roger Pate
1
@Let:已经指出了一个保证:您可以将其移动赋值。 - Roger Pate
我正在点赞以抵消负评。@Roger,你在问一个类型模拟MoveConstructible的对象有什么保证。仅仅可以对对象应用std::move并不意味着你可以对其进行赋值。 - avakar
@Roger,我不明白,我的前一个观点是不正确的吗?(我在为答案辩护,因为我认为在给定的问题下,“你决定”和“没有保证”的答案是正确的。现在我意识到你可能不是那个投反对票的人,所以我的评论可能被误导了。) - avakar
@avakar:啊,你假设我是那个点踩的人更有道理,但“完全由你决定”并没有帮助。例如,如果您想要使用具有交换或容器的类型,则需要MoveAssignable。 “让”他说他误解了我;我以为他在发布第二篇文章后会删除这篇回答。我正在寻求“合理”的要求和指导,这些也不在这里。 - Roger Pate
显示剩余5条评论

1

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

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

编辑。

通常,std::move应该用于将移动语义应用于非rvalue的变量,但实际上它是:

SomeClass :: SomeClass(SomeClass&& v)
{
    // 在这个函数内部,v不再是rvalue。但我知道实际上这是rvalue,并使用std::move
    OtherFunction(std :: move(v));
}

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

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

@Roger,你正在编写可移植的代码,保持对象的可用状态是你的责任。被“占用”的资源应该表现为默认的空对象。如果这不可能实现,那么对象方法必须抛出一些已知和记录的异常或返回失败(例如.NET中的ObjectDisposedException)。这就是我在编写带有&&运算符的类时会做的事情。但是,我不知道标准是否要求这样做。 - Alex F
@Let: 未定义行为 - Roger Pate
@Alex 如果你不能移动,为什么要让你的类可移动呢? - Šimon Tóth
@Alex:我不是在编写移动代码,而是在编写一个以T为模板参数的函数,用于移动对象。T可以是从int到std::vector甚至其他任何类型的对象。关于“如果这是不可能的,对象方法必须抛出一些已知的异常……”- 这是在哪里规定的,还是只是你的建议?(听起来像是个好建议,但不够强烈,不符合我的要求。) - Roger Pate
@Roger 因为那样就没有逻辑意义了。当标准允许你实现某些东西时,这意味着它本身不是未定义行为。正如我已经说过的,你可以称之为已定义行为。 - Šimon Tóth
显示剩余12条评论

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接