C++ 移动语义==自定义交换函数过时?
最近,关于如何提供自己的C++ 移动语义==自定义交换函数过时?,c++,c++11,swap,move-semantics,obsolete,C++,C++11,Swap,Move Semantics,Obsolete,最近,关于如何提供自己的swap功能。使用C++11,std::swap将使用std::move和move语义尽可能快地交换给定的值。当然,这仅在提供移动构造函数和移动赋值运算符(或使用“按值传递”的运算符)时有效 现在,有了这些,是否真的有必要在C++11中编写自己的swap函数?我只能想到不可移动的类型,但同样,自定义的交换通常通过某种“指针交换”(也称为移动)来工作。也许有一些参考变量?嗯……这是一个判断的问题。我通常会让std::swap完成原型代码的工作,但对于发布代码,我会编写一个自
swap
功能。使用C++11,std::swap
将使用std::move
和move语义尽可能快地交换给定的值。当然,这仅在提供移动构造函数和移动赋值运算符(或使用“按值传递”的运算符)时有效
现在,有了这些,是否真的有必要在C++11中编写自己的
swap
函数?我只能想到不可移动的类型,但同样,自定义的交换通常通过某种“指针交换”(也称为移动)来工作。也许有一些参考变量?嗯……这是一个判断的问题。我通常会让std::swap
完成原型代码的工作,但对于发布代码,我会编写一个自定义的swap。我通常可以编写一个自定义交换,速度大约是1个移动构造+2个移动分配+1个无资源破坏的两倍。然而,人们可能希望等到std::swap
实际证明是一个性能问题后再动手
阿尔夫·p·斯坦巴赫的更新:
20.2.2[utility.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
);
std::swap
也不例外,即使没有移动成员。可能有一些类型可以交换,但不能移动。我不知道有任何不可移动的类型,所以我没有任何示例。按照惯例,自定义交换不提供抛出保证。我不知道std::swap
。我对委员会在这方面工作的印象是,这一切都是政治性的,因此,如果他们在某个地方将duck
定义为bug
,或者类似的政治文字游戏,我也不会感到惊讶。因此,我不会依赖于这里的任何答案,除非它提供了一个从C++0x到标准的详细的逐条引用,包括最小的细节(以确保没有bug)。当然,您可以按照
template <class T>
void swap(T& x, T& y)
{
T temp = std::move(x);
x = std::move(y);
y = std::move(temp);
}
它不必运行构造函数和析构函数,只需交换指针(很可能实现为XCHG或类似的东西)
当然,在第一个示例中,编译器可能会优化掉构造函数/析构函数调用,但如果它们有副作用(即调用new/delete),则可能没有足够的智能来优化它们。考虑以下类,该类包含内存分配的资源(为简单起见,用一个整数表示):
然后,std::swap
将导致删除空指针3次(对于移动赋值运算符和统一赋值运算符情况)。编译器可能无法优化此类删除操作,请参阅
自定义swap
不调用任何delete
,因此可能更有效。我想这就是为什么std::unique_ptr
提供定制std::swap
专门化的原因
更新
看起来Intel和Clang编译器能够优化空指针的删除,而GCC则不行。有关详细信息,请参阅
更新
似乎有了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&)不例外代码>是一个打字错误,或者可能是吞下了符号?也就是说,你的意思是A(A&&)noexcept代码>(分配操作员同上)?干杯,阿尔夫:不;他给出了一个例子,其中std::swap
即使没有实际的移动构造/赋值,也将noexcept(true)
。@Dennis:我想我需要更多的解释,因为对我来说,它看起来像一个普通的(非移动)复制构造函数和一个普通的复制赋值运算符,据我所知,那些与无关的是可构造的还是可分配的?@Alf:是可构造的还是可构造的。复制构造函数的传统T(const T&)
版本可以很好地接受r值引用,因此T
将报告为可移动构造,即使实际移动没有发生。从is_nothrow\u move_assignable
到is_nothrow\u assignable
执行了类似的映射,因此相同的参数适用于此。@丹尼斯:谢谢大家的解释,你完全正确。我现在要回到这一点。如果您的移动构造函数/赋值运算符的副作用没有反映在您的自定义交换中,我怀疑您的实现是错误的。std::array是可复制的,但没有移动构造函数,如果这是您的意思的话。一个不可移动的东西:来自Win32 API的关键部分
看起来像一个。它是一个不透明类型,必须初始化,不能复制,并且内部有一个指向自身的指针。一个可移动的包装器必须有一个指针来管理它。
void swap(A& x, A& y)
{
using std::swap;
swap(x.ptr, y.ptr);
}
class 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); }
// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
// rhs.i_ = nullptr; return *this; }
~X() { if (i_) delete i_; }