【发布时间】:2014-07-23 17:27:51
【问题描述】:
正如this answer 中所解释的,copy-and-swap 习惯用法如下实现:
class MyClass
{
private:
BigClass data;
UnmovableClass *dataPtr;
public:
MyClass()
: data(), dataPtr(new UnmovableClass) { }
MyClass(const MyClass& other)
: data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
MyClass(MyClass&& other)
: data(std::move(other.data)), dataPtr(other.dataPtr)
{ other.dataPtr= nullptr; }
~MyClass() { delete dataPtr; }
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, other.data);
swap(first.dataPtr, other.dataPtr);
}
MyClass& operator=(MyClass other)
{
swap(*this, other);
return *this;
}
};
通过将 MyClass 值作为 operator= 的参数,该参数可以由复制构造函数或移动构造函数构造。然后,您可以安全地从参数中提取数据。这可以防止代码重复并有助于异常安全。
答案提到您可以交换或移动临时变量。它主要讨论交换。但是,如果没有经过编译器优化,交换会涉及三个移动操作,并且在更复杂的情况下会做额外的额外工作。当您想要的只是将临时对象移动到分配对象中。
考虑这个更复杂的例子,涉及observer pattern。在此示例中,我手动编写了赋值运算符代码。重点是move构造函数、赋值运算符和swap方法:
class MyClass : Observable::IObserver
{
private:
std::shared_ptr<Observable> observable;
public:
MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
friend void swap(MyClass& first, MyClass& second)
{
//Checks for nullptr and same observable omitted
using std::swap;
swap(first.observable, second.observable);
second.observable->unregisterObserver(first);
first.observable->registerObserver(first);
first.observable->unregisterObserver(second);
second.observable->registerObserver(second);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
}
显然,这个手动编写的赋值运算符中的重复部分代码与移动构造函数中的重复部分相同。您可以在赋值运算符中执行交换,并且行为是正确的,但它可能会执行更多移动并执行额外的注册(在交换中)和取消注册(在析构函数中)。
重用移动构造函数的代码不是更有意义吗?
private:
void performMoveActions(MyClass&& other)
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
public:
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
performMoveActions(other);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
performMoveActions(other);
}
在我看来,这种方法绝不逊色于交换方法。我是否认为在 C++11 中将复制和交换习语作为复制和移动习语会更好,还是我错过了一些重要的事情?
【问题讨论】:
-
需要注意的是,
std::swap的默认实现将使用移动构造函数,如果你不自己专门化它,导致代码几乎等同于你的第二个示例。 -
@ComicSansMS 不过,它将使用一个移动构造和两个移动分配。无论如何,移动代码已经存在,对于移动构造函数,所以似乎不需要使用交换来代替重用移动构造函数代码。
-
@Aberrant:对你的编译器表现出一点信心,它擅长消除无用的东西。
-
这个问题非常冗长,可能不到这个长度的一半。你能把它清理干净吗?
-
@mydogisbox 好点,我会试试的。
标签: c++ c++11 move-semantics assignment-operator copy-and-swap