18

Possible Duplicate:
how to provide a swap function for my class?

Every time I think I understand it, I see something that really confuses me.

If you want to provide an implementation of swap for your own class, what do you do?

The list of possibilities is:

  1. Define one inside the std namespace (taking two arguments), which calls #3 below
    (some say this is correct; some people say illegal)

  2. Define a static method inside the class, taking two arguments to swap
    (makes more sense to me than #4, but I don't get why no one does this), which calls any base-class swaps as necessary

  3. Define an instance method inside the class, taking one other argument to swap with, which calls any base-class swaps as necessary

  4. Define an instance method inside the class, taking two other arguments to swap, also here, which calls any base-class swaps as necessary

  5. Define one in your own namespace (taking two arguments), which calls #3

  6. Something else

My own understanding is that I need #5 and #3, where the caller would then be calling swap likeusing std::swap; swap(a, b);,but the fact that no one seems to suggest that combination is really confusing me. And I really don't understand #4 at all, because everyone seems to be using an instance member when in fact the operation is static. I can't tell if my understanding is wrong or a bunch of the answers I see when looking this up.

What's the correct way?

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 4
    @Xeo: Did you actually *read* my question before marking it as a dupe? The *entire reason* I posted it was that the "dupes" were confusing me. – user541686 Jul 24 '12 at 16:26
  • 1
    Note your misunderstanding on #4, it's not actually an instance function but a `friend` function, aka the same as #5 really. – Xeo Jul 24 '12 at 16:27
  • Well, it seemed to me you missed the question I linked, and I find I argue quite well which version to use why. :) – Xeo Jul 24 '12 at 16:29
  • @Xeo: Whoa... would you post that as an answer here then? (Edit: or... looks like someone beat you to it.) I totally didn't realize it's a "friend function" (never defined one of those... didn't know they're static) – user541686 Jul 24 '12 at 16:29
  • 2
    A friend function is not static: a static member is still a member of the class, a friend is not. Also, in C++ the correct terminology is "member function" not "instance method". – Jonathan Wakely Jul 24 '12 at 17:51
  • @JonathanWakely: I meant "static" as in "not bound to an instance"... I guess in C++ you would call that 'free'? I wasn't really worrying about the terminology so much as trying to get the idea across. – user541686 Jul 24 '12 at 18:56
  • 1
    Yes, 'free' or 'non-member' works, remember C++ is not an OO language, things are not members or 'instance-specific' by default. 'static' is a heavily overloaded term in C++, you'll probably get ideas across better by avoiding using that word unless you mean it in a technical sense – Jonathan Wakely Jul 24 '12 at 20:30

3 Answers3

8

A common pattern I have seen is providing 3 and 5, as per your own understanding.

  1. adds an specialization to the std:: namespace, which is allowed, but might not be possible in all cases (if your type is a template itself).
  2. offers no advantage at all, and forces qualifying with the type when used outside of one of the members, which means that for implementing swap on other types that hold your type as a member, they will need to qualify the call (void swap( other& l, other& r ) { T::swap( l.t, r.t ); })
  3. does not need friendship, allows for use with rvalues (even in C++03) and is idiomatic in some cases std::vector<int>().swap( v ); to clear the contents of the vector.
  4. What? You misunderstood the code! That is not declaring a member taking two arguments, but rather a free function taking the two arguments, and defines the function inline. This is equivalent to 5 (without forwarding to 3, but rather implementing everything in the free function).
  5. Free function in the same namespace allows for ADL to find it, and enables other code to use the common pattern of void swap( other& l, other& r ) { using std::swap; swap( l.t, r.t ); } without having to know whether the type of other::t has an specific swap overload or the one in std:: needs to be used. Forwarding to 3 allows you to provide a single (real) implementation that can be used through ADL and also on temporary objects.
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    as per #1, specialization is allowed, overloading is not. Also, you can specialize it for template classes just fine. – Mooing Duck Jul 24 '12 at 21:26
  • 1
    @MooingDuck: *you can specialize it for template classes just fine*? You can provide specializations of `swap` for any specialization for your template, but you cannot provide an specialization of `swap` that generically implements the functionality across all the instantiations of your template. That is, you can provide `void std::swap( mytemplate&, mytemplate& )`, but not `template std::swap( mytemplate&, mytemplate& );` --which is not an specialization of the standard swap, but rather a different base template. – David Rodríguez - dribeas Jul 24 '12 at 23:13
  • 1
    Oh, I guess you're right that it wouldn't be a specialization. My bad. – Mooing Duck Jul 24 '12 at 23:22
8

The C++11 standard says an object t is swappable with an object u if swap(t, u) and swap(u, t) are valid expressions that select non-member functions called swap from an overload set that includes the two std::swap templates in <utility> and any overloads found by Argument Dependent Lookup, such that the values of t and u are exchanged.

The conventional way to swap two objects under the conditions described above is:

using std::swap;
swap(t, u);

Here name lookup will consider std::swap and any overloads of swap found by ADL, then overload resolution will pick the best match.

Considering each of your implementations:

  1. It is not legal to add overloads to namespace std, and the rule above doesn't require them to be found anyway. It is legal to specialize the standard std::swap function templates if the specialization is for a user-defined type (i.e. not a standard library type or fundamental type.) If you specialize std::swap it's valid, otherwise not, and you can't partially-specialize function templates.

  2. A static member function will not be found by swap(t, u) because it needs to be qualified e.g. Foo::swap(t, u)

  3. A unary swap member function cannot be called as swap(t, u), it has to be called as t.swap(u)

  4. The links you give show a non-member friend function, which can be found by ADL, so can be used to make types swappable.

  5. Such a function can be found by ADL.

So 2 and 3 do not make a type swappable. 4 does, and 5 does. 1 might do, if done correctly, but cannot be used for swapping class templates.

3 is not required, but can be used to help implement either 4 or 5, because a member function will have access to the type's internal details so can swap private members. For 4 the function is a friend, so has access already. So by a process of elimination you need either 4 or 3 and 5. It is generally considered better to provide a member swap (3) and then provide a non-member function in the same namespace (5) which calls the member swap.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
4

As you say, you need #5 (a function in the same namespace as your type) to support the idiomatic using std::swap; swap(a,b);. That way, your overload will be selected by argument-dependent lookup in preference to std::swap.

If your implementation of swap needs to access the type's privates, then you will either need to call a member function like #3, or declare the non-member function a friend. This is what your examples in #4 do: a friend function can be declared and defined inside the class, but that does not make it a member; it is still scoped within the surrounding namespace.

So this:

class thing {
    friend void swap(thing & a, thing & b) {/*whatever*/}
};

is equivalent to this (more or less - see comments):

class thing {
    friend void swap(thing & a, thing & b);
};

inline void swap(thing & a, thing & b) {/*whatever*/}

#1 (specialising std::swap for your type) is allowed, but some would regard it as cleaner to keep everything in your own namespace.

Neither #2 nor #3 would allow an unqualified swap(a,b) to find your implementation.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 4
    +1, not sure how relevant it is, bu the last two examples are not *exactly* equivalent. In the last example, `swap` is accessible in the namespace level (the definition is also a declaration), while by defining it inside the class, it is only accessible through ADL. In particular this cannot be done if defined inline: `void (*p)(thing&,thing&) = &swap;` – David Rodríguez - dribeas Jul 24 '12 at 16:32
  • @DavidRodríguez-dribeas: That's interesting, I didn't know that. – Mike Seymour Jul 24 '12 at 16:34
  • [Note, I should have been slightly more precise: with *only* the definition as `friend` it is not accessible at namespace level, the two examples would be equivalent if in the first one you added a declaration at namespace level: `class thing {...}; void swap( thing&, thing& );` -- the problem is that the `friend` declaration only declares the function *inside* the context of the class, and does not provide a namespace level declaration] – David Rodríguez - dribeas Jul 24 '12 at 22:58