在类的赋值运算符中,您通常需要检查被分配的对象是否是调用对象,这样您就不会搞砸了:
Class& Class::operator=(const Class& rhs) {
if (this != &rhs) {
// do the assignment
}
return *this;
}
移动赋值运算符是否需要相同的东西?是否存在 this == &rhs
会是真的情况?
? Class::operator=(Class&& rhs) {
?
}
原文由 Seth Carnegie 发布,翻译遵循 CC BY-SA 4.0 许可协议
首先, 复制和交换 并不总是实现复制分配的正确方法。几乎可以肯定,在
dumb_array
的情况下,这是一个次优解决方案。Copy and Swap 的使用是针对
dumb_array
将最昂贵的操作与最完整的功能放在底层的经典示例。它非常适合想要最完整功能并愿意支付性能损失的客户。他们得到了他们想要的。但对于不需要最完整功能而是寻求最高性能的客户来说,这是灾难性的。对他们来说
dumb_array
只是另一个他们必须重写的软件,因为它太慢了。如果dumb_array
的设计不同,它可以满足两个客户,而不会对任何一个客户妥协。满足这两个客户的关键是在最低级别构建最快的操作,然后在其之上添加 API 以获取更完整的功能,但成本更高。即,您需要强大的例外保证,很好,您需要为此付费。你不需要吗?这是一个更快的解决方案。
让我们具体一点:这是
dumb_array
的快速、基本的异常保证复制赋值运算符:解释:
您可以在现代硬件上做的更昂贵的事情之一就是去堆。你可以做的任何事情来避免去堆是花费时间和精力。
dumb_array
的客户可能希望经常分配相同大小的数组。当他们这样做时,您需要做的就是memcpy
(隐藏在std::copy
下)。您不想分配一个相同大小的新数组,然后释放相同大小的旧数组!现在,对于真正需要强大异常安全性的客户:
或者,如果您想利用 C++11 中的移动赋值,则应该是:
如果
dumb_array
的客户重视速度,他们应该调用operator=
。如果他们需要强大的异常安全性,他们可以调用通用算法,这些算法将适用于各种各样的对象,并且只需要实现一次。现在回到最初的问题(此时有一个类型-o):
这实际上是一个有争议的问题。有些人会说是的,绝对的,有些人会说不。
我个人的意见是不,你不需要这个检查。
理由:
当一个对象绑定到一个右值引用时,它是以下两种情况之一:
如果您对一个实际临时对象的引用,那么根据定义,您对该对象具有唯一的引用。它不可能被整个程序中的其他任何地方引用。即
this == &temporary
是不可能的。现在,如果您的客户对您撒谎并承诺您会得到一个临时的,而实际上您并没有,那么客户有责任确保您不必在意。如果你想非常小心,我相信这将是一个更好的实现:
即,如果您 被 传递一个自我引用,这是客户端的一个应该修复的错误。
为了完整起见,这里是
dumb_array
的移动赋值运算符:在移动分配的典型用例中,
*this
将是一个移动对象,因此delete [] mArray;
应该是一个空操作。实现尽可能快地在 nullptr 上进行删除至关重要。警告:
有些人会争辩说
swap(x, x)
是一个好主意,或者只是一个必要的邪恶。如果交换进入默认交换,这可能会导致自移动分配。我不同意
swap(x, x)
是 个 好主意。如果在我自己的代码中发现它,我会认为它是一个性能错误并修复它。但是,如果您想允许它,请意识到swap(x, x)
仅对已移动的值执行 self-move-assignemnet。在我们的dumb_array
示例中,如果我们简单地省略断言,或者将其限制为移动的情况,这将是完全无害的:如果您自行分配两个移出(空)
dumb_array
,除了在程序中插入无用指令之外,您不会做任何不正确的事情。对于绝大多数物体都可以进行同样的观察。<
更新>
我对这个问题有了更多的思考,并稍微改变了我的立场。我现在认为assignment应该可以容忍self assignment,但是copy assignment和move assignment的post条件不同:
对于复制分配:
应该有一个后置条件,即不应更改
y
的值。当&x == &y
那么这个后置条件转换为:自我复制分配应该对x
的值没有影响。对于移动分配:
应该有一个后置条件,即
y
具有有效但未指定的状态。当&x == &y
那么这个后置条件转换为:x
具有有效但未指定的状态。即自移动分配不必是空操作。但它不应该崩溃。这个后置条件与允许swap(x, x)
正常工作是一致的:上述工作,只要
x = std::move(x)
不崩溃。它可以使x
处于任何有效但未指定的状态。我看到了三种方法来为
dumb_array
移动赋值运算符来实现这一点:上面的实现允许自赋值,但是
*this
和other
在自移动赋值后最终是一个零大小的数组,不管*this
的原始值是多少---
是。这可以。上述实现与复制赋值运算符一样,通过使其成为无操作来容忍自赋值。这也很好。
仅当
dumb_array
不包含应“立即”销毁的资源时,上述内容才可以。例如,如果唯一的资源是内存,上面的就可以了。如果dumb_array
可能持有互斥锁或文件的打开状态,则客户端可以合理地期望移动分配的 lhs 上的那些资源会立即释放,因此这种实现可能会出现问题。第一个的成本是两个额外的商店。第二个成本是测试和分支。两者都有效。两者都满足 C++11 标准中表 22 MoveAssignable 要求的所有要求。第三个也适用于非内存资源问题。
根据硬件的不同,这三种实现可能有不同的成本:一个分支有多贵?有很多寄存器还是很少?
要点是,与自复制分配不同,自移动分配不必保留当前值。
<
/更新>
受 Luc Danton 评论启发的最后一次(希望如此)编辑:
如果您正在编写一个不直接管理内存的高级类(但可能有这样做的基类或成员),那么移动分配的最佳实现通常是:
这将依次分配每个基数和每个成员,并且不包括
this != &other
检查。假设您的基础和成员之间不需要维护不变量,这将为您提供最高的性能和基本的异常安全性。对于要求强大的异常安全性的客户,请将他们指向strong_assign
。