std::swap
来处理,但是对于发布的代码则需要编写自定义的交换函数。我通常可以编写一个自定义的交换函数,其速度大约是1个移动构造+2个移动赋值+1个无资源销毁的2倍。不过,在出现性能问题之前,我们可能需要等待std::swap
真正成为一个问题。std::swap(T&, T&)
具有等效于noexcept
的特点:template <class T>
void
swap(T& a, T& b) noexcept
(
is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value
);
也就是说,如果对于 T
类型的移动操作是 noexcept
的话,那么对于 T
类型的 std::swap
也是 noexcept
的。
需要注意的是,该规范并不要求存在移动成员。它只要求从右值构造和赋值,并且如果它是 noexcept
的,则交换操作将是 noexcept
的。例如:
class A
{
public:
A(const A&) noexcept;
A& operator=(const A&) noexcept;
};
std::swap<A>
是noexcept的,即使没有移动成员也是如此。
template <class T>
void swap(T& x, T& y)
{
T temp = std::move(x);
x = std::move(y);
y = std::move(temp);
}
但是我们可能有自己的类,比如说 A
,我们可以更快地进行交换。
void swap(A& x, A& y)
{
using std::swap;
swap(x.ptr, y.ptr);
}
这个方法不需要运行构造函数和析构函数,只需交换指针(可能是通过XCHG或类似的方式实现)。
当然,编译器可能会优化掉第一个例子中的构造函数/析构函数调用,但如果它们具有副作用(即对new/delete的调用),则可能无法聪明地将它们优化掉。
CRITICAL_SECTION
看起来就像一个。它是一种不透明类型,必须被初始化,不能被复制,并且内部有一个指向自身的指针。要围绕它创建可移动的包装器,必须要有一个指针来管理它。 - Joker_vDclass X {
int* i_;
public:
X(int i) : i_(new int(i)) { }
X(X&& rhs) noexcept : i_(rhs.i_) { rhs.i_ = nullptr; }
// X& operator=(X&& rhs) noexcept { delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
X& operator=(X rhs) noexcept { swap(rhs); return *this; }
~X() { delete i_; }
void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); }
};
void swap(X& lhs, X& rhs) { lhs.swap(rhs); }
然后,std::swap
会导致删除 null 指针 3 次(包括移动赋值运算符和统一赋值运算符)。编译器可能无法优化这样的 delete
,参见https://godbolt.org/g/E84ud4。
自定义的 swap
不会调用任何 delete
,因此可能更有效率。我猜这就是为什么 std::unique_ptr
提供自定义的 std::swap
特化的原因。
更新
似乎 Intel 和 Clang 编译器能够优化出空指针的删除,然而 GCC 不行。详情请参见Why GCC doesn't optimize out deletion of null pointers in C++?。
更新
在 GCC 中,我们可以通过以下方式重写 X
来避免调用 delete
运算符:
// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
~X() { if (i_) delete i_; }
A(const A&) noexcept;
是一个打字错误,或者可能是SO吞噬了&符号?也就是说,你的意思是A(A&&) noexcept;
(赋值运算符也一样)?干杯! - Cheers and hth. - Alfstd::swap
也会成为noexcept(true)
。 - Dennis Zickefooseis_nothrow_move_constructible
或is_nothrow_move_assignable
没有任何关系? - Cheers and hth. - Alfis_nothrow_move_constructible<T>
与is_nothrow_constructible<T,T&&>
相同。传统的T(const T&)
版本的复制构造函数可以很好地接受r-value引用,因此即使实际上没有移动,T
也会报告为可移动构造,类似的映射从is_nothrow_move_assignable<T>
到is_nothrow_assignable<T, T&&>
也是如此,因此相同的论点适用于那里。 - Dennis Zickefoose