C++ the good parts — std::swap

std::swap is quite fundamental. And allow me to assume, fundamentally glanced over by you :) Which is a pity; understanding std::swap concept fully, will make you more clear on a few other important C++ concepts.

Advice: to understand C++ swap one must be sure of the other C++ fundamentals fully. Please, if in doubt, stop reading and jump to the link provided in the text in bold font, for a quick refresh and then jump back.

 

The easy part

Two easy things to mention before we dive in.

Canonical swap implementation on your class

Here is how you provide your own swap for your class/struct :

And this is how it will be called/used:

If not sure, look up ADL. Now let us proceed.

Swap is not Exchange

Exchange of values is the other fundamental and distinctive operation.  The language you code in must allow you to clearly separate the fundamental operations.  The key difference between swap and exchange is the lifetime of values swapped or exchanged.

Swap ( A , B )  // B life is over, it is a zombie. Do not deal with zombies

Exchange (A, B) // both B and A are alive and well, content exchanged

Caveat Emptor: here we use the word “exchange” as normal people do. In case you know or do not want to know about std::exchange please see here.

Most importantly: B is said to be moved from after swap is done. (hint: yup, here is that link to the refresher, in bold ). So.

Let’s Move

The move is a fundamental building block. The synopsis std::swap is:

That is a generic function. The use of std::move assures first, the move is conceptually implemented and executed and second, values are moved for all the types involved for arguments a and b above.

The swap concept is using the move concept. Move concept is the fundamental building block to value semantics of the to modern C++.  The move is fundamental in making value semantics feasible. With the addition of “move constructor” and “move assignment”.

To understand and use for your own benefit the moving and swapping in C++ please always be aware of what type category, exactly is being moved or swapped. We will focus on the differences regarding the types of values moved or swapped.

In here we will peruse three particular categories of types to form the facets of the C++ moving/swapping. We will name them into three groups for this post:

  1. Fundamental Types
  2. User-Defined Trivial Types
  3. User-Defined Non-Trivial Types

There is a bit more to this whole subject. I might suggest you to start from here. Before proceeding please be sure you are clear on the C++ type system.

Swapping fundamental types

This type category is here as a natural and good starting point on the std::swap journey of discovery.  There is nothing surprising about swapping fundamental types.

The most important thing to notice here:  B is moved from. C++ standard does mention that explicitly: Bi is in the “undefined state” after the move of its value to A.  Remember: Swap is not Exchange.

You obviously can see it, B is actually alive and well, and equals 2, as you have expected. But keep in mind C++ is a language based on abstract thinking and abstractions. Thus the swap definition is carefully made so it applies to all the types of A and B above.  Above just happens to be two integers. But please always have this mental picture

That is actually standardized.  It is very precisely defined what you can swap and what you can not swap.   Any type T can be swapped as long as it is Swappable , which in code can be (and it is) checked with std::is_swappable and friends.

And crucially there is always one left in the “undefined state” because it was “moved from” inside a swap operation. Now onto the next group of types.

[nextpage title=”User-Defined Trivial Types”]
User-Defined Trivial Types

Are more or less simple structs (or classes) with no methods or constructors, made by you. Refresher starts here.

To analyze moving and swapping of such types we will use

Let’s start by moving an instance of the above.

Class Data Containment

The above layout is trivial among other things because the above Capsule instance contains all of its data.  Basically it has no pointers to elsewhere. It is one self-contained memory block.

That is important for swap or move. Basically compiler is swapping memory blocks when dealing with standard layout.

Above is indeed swapped.   The compiler can do this because each instance above is in its own memory; the situation is laid out as two memory blocks.

Quick recap. Here is the visualization of the swap( A, B ) operation. Values swapping.

Values swapping

Please note: Temporary is local to the swap function. Again, please remember: By C++ standard, B value is moved from. And left in this curious “undefined” state. Do not use it as it is moved from.

That’s it. Done. Let’s go out and have some fun?! Well … not yet.  What comes is:

Native Pointers are not Fundamental Types

I will ask again: you are clear on the C++ type system right? In particular, you are now sure where do native pointers fit in. Good, but why this matters now?

std::move applied to values has full effect. But,  after the move operation native pointers are left as they are. Pointers are:

  1. Not fundamental types
  2. Just memory addresses

std::move on a native pointer has no effect.

What exactly happens if you move from the pointer?

Basically nothing happens. You have two pointers pointing to the same memory  address with the single value inside

Two same pointers

std::iter_swap

Lets now swap two pointers.  In C++ lingo pointers are iterators. There is std::iter_swap for swapping two pointers, aka iterators. What do you expect will happen?

Follow the above through debugger. Surprise?  The result is the same as if we have swapped two values not pointers, two instances of capsule.  Which is not a surprise if you peek into the synopsis of  the std::iter_swap :

It simply dereferences the pointers and calls the std::swap on two instances.

The outcome for pointers (aka iterators) of both trivial types and fundamental types is:  pointers have not changed, and the values are. It is as simple as that.

But that simplicity yields complexity when dealing with the next type category.

[nextpage title=”Non-Trivial User-Defined Types”]
Swapping Non-Trivial User-Defined Types

As you know for the simple pod-like type we have used above, the compiler has generated all the necessary scaffolding, constructors and the rest. And the whole section was almost boring?

Let us create and use the simple but nontrivial type to analyze moving and swapping in scenarios where native pointers are the members.

Remember: we have left it to the trusty C++ compiler to generate implicit destructors and assignment operators. All six of them. Good. Lets do the simple value move first

The result is an application crash because of Access violation. Let’s repeat the above but with comments

Implicitly generated move ctor moved over just the pointer from the specimen, not the actual heap-allocated data.  Thus resulting in two pointers to the same memory.  Thus the second free from the second destructor provoked a crash.

Not to worry. Let’s add the move constructor.

Which as we see, in turn, provoked the need for default ctor and for the swap()  friend, where we implement data pointers rewiring.

Basically we have added user-defined move constructor, swap and default constructor. And move value worketh after these changes. As did the value swapping too.

Above using is the standard idiom to pacify the ADL.  What about pointer tests? Let’s try and move the pointer.

And swapping the pointers worketh too

That is an absolutely minimal non-trivial type implementation that is swappable. Please observe what scaffolding we had to implement to have moved off our type of work.

Conclusion

Terminology is important and hard to grasp. Flexibility brings complexity. Modern C++ is very flexible. That means your move implementation can be very finely tuned for non-trivial types you create…

Modern C++ is built on a few of these mechanisms. I do hope by reading this long post you gained a better understanding of the intricacies of swapping, moving and value-based semantics.

[nextpage title=”Appendix”]

Appendix A

 

Pointer Rewiring aka Relinking

 

Since a “pointer” is a much-feared word, let’s clarify what is pointer rewiring/relinking. That is the idiom used to speed up nontrivial types moving. You do not move them, you swap their internals.

Let’s visualize this function

Watch carefully.

Pointer rewiring START

That is the situation upon entering the rewiring function. Then:

Pointer rewiring step ONE

And then this:

Pointer rewiring step TWO

This is where “pointer to A” loses its address. It is rewired to the same value as pointer B. Lastly:

Pointer rewiring step THREE

Now B is rewired to the “value A”. Effectively pointers A and B have exchanged addresses of the values they have been pointing to. The pointers themselves have changed. They have been  “rewired”.