Move semantics == custom swap function obsolete?
Asked Answered
M

5

36

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...

Mader answered 20/6, 2011 at 19:30 Comment(0)
K
27

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.

Kappel answered 20/6, 2011 at 19:40 Comment(7)
Thanks for the update, Howard. I'm guessing that A(const A&) noexcept; is a typo, or perhaps SO swallowing ampersands? I.e. that you meant A(A&&) noexcept; (and ditto for the assignment opperator)? Cheers,Goidelic
@Alf: No; he is giving an example where std::swap will be noexcept(true) even without actual move construction/assignment.Untangle
@Dennis: I think I need more explanation then, because to me that looks like an ordinary (not move) copy constructor, and an ordinary copy assignment operator, and as I understand it those have nothing to do with is_nothrow_move_constructible or is_nothrow_move_assignable?Goidelic
@Alf: is_nothrow_move_constructible<T> is the same as is_nothrow_constructible<T, T&&>. The traditional T(const T&) version of the copy constructor accepts an r-value reference just fine, so T will report as being move constructible, even if an actual move does not take place. A similar mapping is performed from is_nothrow_move_assignable<T> to is_nothrow_assignable<T, T&&>, so the same argument applies there.Untangle
@Dennis: Thanks for the explanation to everyone, you're exactly correct. I'm just now getting back to this.Kappel
hmm i cannot find that "duck bug" footnote in n3290.Baroque
Search for "duck" on this page. ;-)Kappel
K
3

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.

Katydid answered 21/6, 2011 at 4:41 Comment(1)
If your move constructor/assignment operator have side effects not reflected in your custom swap, I suspect you've implemented them wrong.Untangle
S
1

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.

Selfconsistent answered 20/6, 2011 at 19:41 Comment(2)
std::array<int> are copyable but have no move constructor, if that's what you meant.Limp
A non-movable thing: CRITICAL_SECTION from Win32 API looks like one. It's an opaque type, it must be initialized, it cannot be copied, and it has a pointer to itself inside. Movable wrapper around one would have to have a pointer to manage it.Hibernicism
G
0

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

Goidelic answered 20/6, 2011 at 21:10 Comment(0)
G
0

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_; }
Gamosepalous answered 15/8, 2017 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.