2021 Aug Update
Clarifications added, ommissions and typos fixed. Importantly Godbolt implementation was added.
Caveat Emptor: this is one long post. It covers a lot. And for C++ newcomers it is full of unavoidable subjects. Please read and re-read. And most importantly ask questions in comments. And please do suggest improvements.
std::swap
is quite fundamental. And allow me to assume, fundamentally glanced over by you :) This is a pity; understanding std::swap
concepts fully will make you more clear on a few other important C++ concepts.
Advice: to understand C++ swap one must be having a firm grasp 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 start
Two easy things to “tickle your fancy” before we dive in.
Canonical swap implementation for your class
Here is how you provide your own swap for your class/struct :
1 2 3 4 5 6 7 8 9 10 11 12 |
template<typename Type> struct my_struct { // this position will make sure ADL will find // this swap first friend void swap( my_struct & a, my_struct & b) { // std::swap is the default or fallback using std::swap; swap(a.data, b.data); } // keep some data, by value Type data; } |
And this is how it will be called/used:
1 2 3 4 5 6 7 8 |
void main() { // std::swap is the default aka fallback using std::swap; my_struct <int> a { 42 } , b { 13 }; // ADL finds the swap we defined swap(a, b); assert( a.data == 13 ); } |
If not sure what it is, look up the ADL now. Now let us proceed.
Swap is not Exchange
Exchange of values is the other fundamental and distinctive operation. The programing language you code in must allow you to clearly separate the fundamental operations. Swap and Exchange are two fundamental operations. Not one. The key difference between swap and exchange is the lifetime of values swapped or exchanged.
1 2 3 4 |
std::wap ( A , B ) // B life is over, it is officialy a zombie. Do not deal with zombies // how to swap A and B and make no zombies in the process auto previous_A = std::xchange (A, B) // A == B now auto previous_B = std::xchange (B, previous_A) // A and B values are swapped now |
Note: here in this text we would 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 the
std::swap
is done.
(hint: yup, here is that link to the refresher, in bold ). So… Why do we talk “move” when this text is about “swap”?
Let’s Move Forward
The move is a fundamental conceptual building block in C++. The synopsis std::swap
is:
1 2 3 4 5 6 7 8 9 10 11 |
// Synopsis template < typename T> void std::swap ( T & a, T & b ) { // here the move ctor on temp is called T temp = std::move( a ) ; // the move assignment is used here a = std::move( b ); // the move assignment is used here b = std::move( temp ); } |
That is a generic function. The use of std::move
assures it is implemented and executed and second, values are moved for all the types involved for arguments a and b above. Let us emphasize: Moved. Not assigned and not exchanged.
The swap concept is using the move concept. Move concept is the fundamental building block to value semantics of modern C++. The move is fundamental in making value semantics feasible. “move constructor” and “move assignment”; two more fundamental concepts.
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 explain and focus on the differences regarding the types of values moved or swapped.
Here we will peruse three particular categories of types to form the facets of the C++ moving/swapping. A bit unlike the standard, we will name them into three groups, just for this post:
-
Fundamental Types
-
User-Defined Trivial Types
-
User-Defined Non-Trivial Types
These names are somewhat non-existent in C++ standard documents but are true and easy for you to understand right now. There is a bit more to this whole subject. I might suggest you start from here: type system. Before proceeding please be sure you are clear on the C++ types.
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.
1 2 3 4 |
// no surprises is a good news int A = 2, B = 13 ; std::swap(A,B) ; // A is 13 and B is 2 |
The most important thing to notice here: B is moved from. C++ standard does mention that explicitly: B 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
1 2 3 4 5 6 7 8 |
// T can be any complex type // it happens in here it is constructible // with number literals T A = 2, B = 13 ; // it also happens to be swappable // with the inbuilt std::swap std::swap(A,B) ; // A is 13 and B is "undefined"! |
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.
User-Defined Trivial Types
(please go to the next page)