例如,stdlibc++ 有以下内容:
unique_lock& operator=(unique_lock&& __u)
{
if(_M_owns)
unlock();
unique_lock(std::move(__u)).swap(*this);
__u._M_device = 0;
__u._M_owns = false;
return *this;
}
为什么不直接将两个 __u 成员分配给 *this 呢?交换是否暗示 __u 被分配了 *this 成员,只是后来分配了 0 和 false ……在这种情况下,交换正在做不必要的工作。我错过了什么? (unique_lock::swap 只是对每个成员执行 std::swap )
原文由 Display Name 发布,翻译遵循 CC BY-SA 4.0 许可协议
我的错。 (半开玩笑,半不是)。
当我第一次展示移动赋值运算符的示例实现时,我只是使用了交换。然后某个聪明人(我不记得是谁)向我指出,在分配之前破坏 lhs 的副作用可能很重要(例如您的示例中的 unlock() )。所以我停止使用交换进行移动分配。但是使用交换的历史仍然存在,并且还在继续。
在这个例子中没有理由使用交换。它比你建议的效率低。确实,在 libc++ 中,我完全按照您的建议进行操作:
一般来说,移动赋值运算符应该:
像这样:
更新
在评论中有一个关于如何处理移动构造函数的后续问题。我开始在那里回答(在评论中),但是格式和长度限制使得很难创建一个清晰的响应。因此,我将我的回应放在这里。
问题是:创建移动构造函数的最佳模式是什么?委托给默认构造函数然后交换?这具有减少代码重复的优点。
我的回答是:我认为最重要的一点是程序员应该警惕不假思索地遵循模式。在某些类中,将移动构造函数实现为 default+swap 正是正确的答案。这个类可能很大而且很复杂。
A(A&&) = default;
可能会做错事。我认为重要的是要考虑每个班级的所有选择。让我们详细看一下OP的示例:
std::unique_lock(unique_lock&&)
。观察:
A. 这个类相当简单。它有两个数据成员:
// 4 stores, 4 loads void swap(unique_lock& u) { std::swap(m_, u.m_); std::swap(_owns, u.owns_); }
// 4 stores, 2 loads unique_lock(unique_lock&& __u) : m_(u._m), owns_(u._owns) { u.m_ = nullptr; u.owns_ = false; }
// 6 stores, 4 loads unique_lock(unique_lock&& u) : unique_lock() { swap(u); }
”`
第一种方法看起来比第二种方法复杂得多。并且源代码更大,并且有些重复我们可能已经在其他地方编写的代码(例如在移动赋值运算符中)。这意味着有更多的机会出现错误。
第二种方法更简单,可以重用我们已经编写的代码。从而减少错误的机会。
第一种方法更快。如果加载和存储的成本大致相同,可能快 66%!
这是一个经典的工程权衡。天下没有免费的午餐。工程师永远无法摆脱必须做出权衡决定的负担。就在这一刻,飞机开始从空中坠落,核电站开始融化。
对于 libc++ ,我选择了更快的解决方案。我的理由是,对于这门课,无论如何我都最好把它做好;这门课很简单,我做对的机会很高;我的客户会重视绩效。对于不同背景下的不同班级,我很可能会得出另一个结论。