34

最近,出现了很多 关于 如何提供自己的功能的问题。在 C++11 中,将使用和移动语义以尽可能快地交换给定值。当然,这仅在您提供移动构造函数和移动赋值运算符(或使用按值传递的运算符)时才有效。swapstd::swapstd::move

swap现在,鉴于此,是否真的有必要在 C++11中编写自己的函数?我只能想到不可移动的类型,但话又说回来, customswap通常通过某种“指针交换”(又名移动)来工作。也许有某些参考变量?嗯……

4

5 回答 5

24

这是一个判断的问题。我通常会让std::swap做原型代码的工作,但为发布代码编写自定义交换。我通常可以编写一个自定义交换,其速度大约是 1 次移动构建 + 2 次移动分配 + 1 次无资源破坏的两倍。然而,人们可能希望等到std::swap真正证明是性能问题后再去打扰。

Alf P. Steinbach 的更新:

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
                 );

即,如果移动操作Tnoexcept,那么std::swap是。Tnoexcept

请注意,此规范不需要移动成员。它只要求存在来自右值的构造和赋值,如果存在,noexcept则交换将是noexcept。例如:

class A
{
public:
    A(const A&) noexcept;
    A& operator=(const A&) noexcept;
};

std::swap<A>是 noexcept,即使没有移动成员。

于 2011-06-20T19:40:59.793 回答
3

当然,您可以将交换实现为

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),它可能不够聪明,无法将它们优化掉。

于 2011-06-21T04:41:31.787 回答
1

可能有一些类型可以交换但不能移动。我不知道任何不可移动的类型,所以我没有任何例子。

于 2011-06-20T19:41:20.420 回答
0

按照惯例,定制swap提供不投掷保证。我不知道std::swap。我对委员会工作的印象是,这都是政治性的,所以如果他们在某个地方定义duckbug,或类似的政治文字游戏策略,我不会感到惊讶。所以我不会依赖这里的任何答案,除非它通过引用 C++0x 的标准来提供详细的打击,直到最小的细节(以确保没有bug)。

于 2011-06-20T21:10:21.873 回答
0

考虑以下包含内存分配资源的类(为简单起见,由单个整数表示):

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); }

然后std::swap导致删除空指针 3 次(对于移动赋值运算符统一赋值运算符情况)。编译器可能无法优化这样的delete,请参阅https://godbolt.org/g/E84ud4

自定义swap不调用任何delete,因此可能更有效。我想这就是std::unique_ptr提供自定义std::swap专业化的原因。

更新

似乎 Intel 和 Clang 编译器能够优化空指针的删除,但 GCC 不能。请参阅为什么 GCC 不优化 C++ 中空指针的删除?详情。

更新

似乎使用 GCC,我们可以通过如下delete重写来防止调用运算符:X

 // X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
 //                                  rhs.i_ = nullptr; return *this; }
    ~X() { if (i_) delete i_; }
于 2017-08-15T08:33:44.653 回答