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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// 'capsule' is a Synonym for POD /// "Plain Old Data" type /// Remember the scaffolding C++ compiler creates struct capsule final { // for is_trivially_constructible // member initialization must not be used bool data; }; // POD is type for which these resolve to true static_assert(std::is_standard_layout<capsule>{} ); static_assert(std::is_trivial<capsule>{} ); // factory methods inline capsule create(bool data_ = false) noexcept { return {data_}; } inline capsule * create_ptr ( bool data_ = false ) noexcept { return new capsule{ data_ }; } |
Let’s start by moving an instance of the above.
1 2 3 4 5 6 |
capsule specimen = create(); capsule rezult = std::move(specimen); /// specimen is in a moved from aka /// 'undefined state' so technicaly /// we should not do this assert(specimen.data == rezult.data); |
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, the compiler is swapping memory blocks when dealing with standard layout.
1 2 3 |
capsule A= create(false); capsule B = create(true); std::swap(A,B); |
The above is indeed swapped. The compiler can do this (on its own) because each instance above is in its own memory block; the situation is laid out as two memory blocks.
Quick recap. Here is the visualization of the swap( A, B )
operation. Two 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 does this matter now?
std::move
applied to values has full effect. But, after the move operation native pointers are left as they are. Pointers are:
- Not fundamental types
- Just memory addresses
std::move
on a native pointer has no effect.
What exactly happens if you move from the pointer?
1234567 capsule * specimen = create_ptr();// move from the pointer to the pointercapsule * rezult = std::move(specimen);// two equal pointersassert( specimen == rezult );// do not erase twicedelete specimen;
Basically, nothing happens. You have two pointers pointing to the same memory address with the single value inside
std::iter_swap
Let’s 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?
1 2 3 4 5 6 7 8 9 |
capsule * left = create_ptr(false); capsule * right = create_ptr(true); // pointer swamping std function // NOT using std::swap std::iter_swap(left, right); delete left; delete right; |
Follow the above through debugger. Surprise? The result is the same as if we have swapped two values not pointers, two instances of capsule
. This is not a surprise if you peek into the synopsis of the std::iter_swap
:
1 2 3 4 5 6 |
// synopsis template< typename Iterator> void std::iter_swap ( Iterator p1, Iterator p2 ) { std::swap(*p1, *p2); } |
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.
Swapping Non-Trivial User-Defined Types
(please go to next page)