我在another question中读到,在实现移动构造函数时,最好将std :: move移动到初始化列表中,因为如果该成员碰巧是另一个对象,那么将调用该对象移动构造函数。像这样......
//Move constructor
Car::Car(Car && obj)
:
prBufferLength(std::move(obj.prBufferLength)),
prBuffer(std::move(obj.prBuffer))
{
obj.prBuffer = nullptr;
obj.prBufferLength = 0;
}
然而,在我看过的所有示例移动赋值运算符中,没有提到使用std :: move的原因相同。如果成员是一个对象,那么应该使用std :: move吗?像这样......
//Move assignment
Car Car::operator=(Car && obj)
{
delete[] prBuffer;
prBufferLength = std::move(obj.prBufferLength);
prBuffer = std::move(obj.prBuffer);
obj.prBuffer = nullptr;
obj.prBufferLength = 0;
return *this;
}
更新
我很欣赏没有必要在我选择的例子中使用std :: move(很差)但是如果成员是对象我感兴趣。
答案 0 :(得分:3)
在阅读链接的问题之后,我可以看到第二个最受欢迎的答案中的建议是在移动构造函数的初始化列表中使用std::move
,因为无论它是否是原始类型,它都是会做正确的事。我有点不同意这一点,并认为你应该只在适当的时候致电std::move
,但这是个人喜好。
此外,对于您的移动分配运算符,您的方式很好,但我认为应该亲自删除对std::move
的不必要的调用。另一个选择是使用std::swap
,它会为你做正确的事。
Car Car::operator=(Car && obj)
{
std::swap(this->prBufferLength, obj.prBufferLength);
std::swap(this->prBuffer, obj.prBuffer);
return *this;
}
上面的移动赋值运算符和移动赋值运算符之间的区别在于,当您的版本立即解除内存释放时,内存的释放延迟,这在某些情况下可能很重要。
答案 1 :(得分:1)
看起来prBuffer
是指针而prBufferLength
是某种整数类型,因此move
在这种特殊情况下不会有任何差别,因为它们都是基本的类型。
例如,如果prBuffer
是std::string
,则应使用move
强制使用移动构造函数或移动赋值运算符。
答案 2 :(得分:1)
就像C ++和生活中的很多事情一样,对这个问题没有明确的肯定或否定答案。
也就是说,作为一般规则,如果传入的成员将被清除/重置/清空并将传入的成员分配给目标成员将导致调用赋值运算符,那么您将需要使用std :: move以便将调用移动赋值运算符而不是复制赋值运算符。
如果不调用赋值运算符(即只执行浅拷贝),则不需要使用std :: move。
答案 3 :(得分:1)
如果你的成员对象会受益于移动,你当然应该移动它们。另一种策略是在你所回答的答案中证明的,就是交换。交换就像移动,但它在两个方向上移动。如果您的类管理资源,则会影响传入的对象(rvalue)接收不再需要的数据。然后该对象的析构函数会破坏该数据。例如,您的移动赋值运算符可以这样写:
Car Car::operator=(Car && obj)
{
// don't need this, it will be handled by obj's destructor
// delete[] prBuffer;
using std::swap;
swap(prBuffer, obj.prBuffer);
swap(prBufferLength, obj.prBufferLength);
return *this;
}
另请查看Copy-and-Swap成语。这允许您使用相同的赋值运算符进行移动和复制,但有一个轻微的缺点,即自我赋值会导致不必要的副本。
答案 4 :(得分:0)
我相信实现移动分配的更好方法是使用移动构造函数创建一个新的临时对象,然后将其与当前对象交换,就像复制赋值一样。
它不仅可以避免代码重复,还可以防止您意外地犯错误。忘记移动会员。