可能重复:
移动语义 == 自定义交换功能已过时?
这是std::swap
C++11 中的样子:
template<typename T>
void swap(T& x, T& y)
{
T z = std::move(x);
x = std::move(y);
y = std::move(z);
}
当然,如果我的类定义了一个移动构造函数和一个移动赋值运算符,我是否仍然需要专注std::swap
于我自己的类型,或者std::swap
尽可能高效?
可能重复:
移动语义 == 自定义交换功能已过时?
这是std::swap
C++11 中的样子:
template<typename T>
void swap(T& x, T& y)
{
T z = std::move(x);
x = std::move(y);
y = std::move(z);
}
当然,如果我的类定义了一个移动构造函数和一个移动赋值运算符,我是否仍然需要专注std::swap
于我自己的类型,或者std::swap
尽可能高效?
的专业化std::swap
现在是可选的,但不被弃用。理由是性能。
对于原型代码,甚至可能是大量交付代码,std::swap
将非常快。但是,如果您需要从代码中找出每一点,编写自定义交换仍然可以带来显着的性能优势。
考虑这样一种情况,您的类基本上有一个拥有指针,而您的移动构造函数和移动赋值只需要处理那个指针。计算每个成员的机器负载和存储:
移动构造函数:1 次加载和 2 次存储。
移动分配:2 次装载和 2 次存储。
自定义交换:2 次加载和 2 次存储。
std::swap
是 1 次移动构造和 2 次移动分配,或者:5 次装载和 6 次存储。
自定义交换可能仍比std::swap
. 尽管任何时候你试图通过计算负载和存储来计算某物的速度,但两者都会很快变坏。
注意:在计算移动分配的成本时,请确保并考虑到您将移动到移动值(在std::swap
算法中)。这通常抵消了重新分配的成本,尽管是以分支为代价的。
std::swap
既然我们已经有了移动语义,那么专业化是否被弃用了?
不,这是通用版本,但您可以对其进行优化以跳过第三次移动操作。我的偏好是将复制和交换std::swap
习语与为我的课程定制相结合。
这意味着我将拥有:
class Aaaa
{
public:
Aaaa(); // not interesting; defined elsewhere
Aaaa(Aaaa&& rvalueRef); // same
Aaaa(const Aaaa& ref); // same
~Aaaa(); // same
Aaaa& operator=(Aaaa object) // copy&swap
{
swap(object);
return *this;
}
void swap(Aaaa& other)
{
std::swap(dataMember1, other.dataMember1);
std::swap(dataMember2, other.dataMember2);
// ...
}
// ...
};
namespace std
{
template<> inline void std::swap(Aaaa& left, Aaaa& right)
{ left.swap(right); }
}
这将取决于您的类型。
你会将它从 x 移动到 z,从 y 移动到 x,从 z 移动到 y。那是底层表示的三个复制操作(也许只有一个指针,也许还有更多,谁知道)
现在,也许您可以为您的类型创建更快的交换(xor 交换技巧、内联汇编程序,或者您的基础类型的 std::swap 可能更快)。
或者您的编译器也可能擅长优化,并且基本上将两种情况优化为相同的指令(例如将临时寄存器放在寄存器中)。
我个人倾向于始终实现一个交换成员函数,该函数将从多个地方调用,包括移动赋值之类的东西,但 YMMV。
这swap()
会调用一个移动构造函数和 2 个移动分配。我认为一个人可以swap()
为他的特定类型的类写更有效的,比如,
class X
{
int * ptr_to_huge_array;
public:
// ctors, assgn ops. etc. etc.
friend void swap(X& a, X& b)
{
using std::swap;
swap(a.ptr_to_huge_array, b.ptr_to_huge_array);
}
};
无论移动构造函数和赋值运算符的实现如何。