移动语义==自定义交换函数过时了吗?

发布于 2024-11-16 11:14:18 字数 618 浏览 4 评论 0原文

最近,很多问题 弹出窗口介绍如何提供您自己的交换函数。在 C++11 中,std::swap 将使用 std::move 和移动语义来尽快交换给定值。当然,只有当您提供移动构造函数和移动赋值运算符(或使用按值传递的运算符)时,这才有效。

现在,既然如此,实际上是否有必要在 C++11 中编写自己的 swap 函数?我只能想到不可移动类型,但话又说回来,自定义交换通常通过某种“指针交换”(也称为移动)来工作。也许有某些参考变量?嗯...

Recently, many questions pop up on how to provide your own swap function. With C++11, std::swap will use std::move and move semantics to swap the given values as fast as possible. This, of course, only works if you provide a move constructor and a move assignment operator (or one that uses pass-by-value).

Now, with that given, is it actually necessary to write your own swap functions in C++11? I could only think of non-movable types, but then again, the custom swaps usually work through some kind of "pointer exchange" (aka moving). Maybe with certain reference variables? Hm...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

秋凉 2024-11-23 11:14:18

这是一个判断问题。我通常会让 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
                 );

即,如果 T 上的移动操作为 noexcept,则 T 上的 std::swap无例外

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

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

std::swap 是 noexcept,即使没有 move 成员。

It is a matter of judgment. I will typically let std::swap do the job for prototyping code, but for release code write a custom swap. I can usually write a custom swap that is about twice as fast as 1 move construction + 2 move assignments + 1 resourceless destruction. However one may want to wait until std::swap actually proves to be a performance problem before going to the bother.

Update for Alf P. Steinbach:

20.2.2 [utility.swap] specifies that std::swap(T&, T&) has a noexcept equivalent to:

template <class T>
void
swap(T& a, T& b) noexcept
                 (
                    is_nothrow_move_constructible<T>::value &&
                    is_nothrow_move_assignable<T>::value
                 );

I.e. if move operations on T are noexcept, then std::swap on T is noexcept.

Note that this spec doesn't require move members. It only requires that construction and assignment from rvalues exists, and if it is noexcept, then swap will be noexcept. E.g.:

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

std::swap<A> is noexcept, even without move members.

错爱 2024-11-23 11:14:18

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

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

Sure, you can implement swap as

template <class T>
void swap(T& x, T& y)
{
  T temp = std::move(x);
  x = std::move(y);
  y = std::move(temp);
}

But we might have our own class, say A, which we can swap more quickly.

void swap(A& x, A& y)
{
  using std::swap;
  swap(x.ptr, y.ptr);
}

Which, instead of having to run a constructor and destructor, just swaps the pointers (which may well be implemented as XCHG or something similar).

Of course, the compiler might optimize out the constructor/destructor calls in the first example, but if they have side effects (i.e. calls to new/delete) it may not be smart enough to optimize them away.

三寸金莲 2024-11-23 11:14:18

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

There might be some types that can be swapped but not moved. I don't know of any non-movable types, so I don't have any examples.

往日 2024-11-23 11:14:18

按照惯例,自定义交换提供无抛出保证。我不知道 std::swap。我对委员会在这方面的工作的印象是,这都是政治性的,所以如果他们在某个地方将 duck 定义为 bug 或类似的政治文字游戏,我不会感到惊讶演习。因此,我不会依赖这里的任何答案,除非它提供了详细的逐一引用 C++0x 的标准,直到最小的细节(以确保没有 错误)。

By convention a custom swap offers no-throw guarantee. I don't know about std::swap. My impression of the committee's work on that is that it was all political, so it would not surprise me if they somewhere had defined duck as bug, or similar political word-game maneuvers. So I would not rely on any answer here unless it provides a detailed blow by blow quoting from the C++0x to-be-standard, down the smallest detail (so as to be sure no bug).

所有深爱都是秘密 2024-11-23 11:14:18

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

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 次(对于移动赋值运算符统一赋值运算符情况)。编译器可能无法优化此类删除,请参阅https://godbolt.org /g/E84ud4

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

更新

看来 Intel 和 Clang 编译器能够优化空指针的删除,但 GCC 却不能。请参阅 为什么 GCC 不优化删除C++ 中的空指针? 了解详细信息。

UPDATE

看来,使用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_; }

Consider the following class that holds a memory-allocated resource (for simplicity, represented by a single integer number):

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

Then std::swap results in deleting null pointer 3 times (both for move assignment operator and unifying assignment operator cases). Compilers might have problem to optimize out such a delete, see https://godbolt.org/g/E84ud4.

Custom swap does not call any delete and might be therefore more efficient. I guess this is the reason why std::unique_ptr provides custom std::swap specialization.

UPDATE

It seems that Intel and Clang compilers are able to optimize out deletion of null pointers, however GCC isn't. See Why GCC doesn't optimize out deletion of null pointers in C++? for details.

UPDATE

It seems that with GCC, we can prevent invoking delete operator by rewriting X as follows:

 // X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
 //                                  rhs.i_ = nullptr; return *this; }
    ~X() { if (i_) delete i_; }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文