C++ the good parts — std::swap

User-Defined Trivial Types

Contents

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

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

capsule specimen = create();
capsule rezult = std::move(specimen);
/// specimen is in a moved from aka 
/// 'undefined state' so technicaly
/// we should not do this
_ASSERTE(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 compiler is swapping memory blocks when dealing with standard layout.

capsule A= create(false);
capsule B = create(true);
std::swap(A,B);

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?

capsule * specimen = create_ptr();
// move from the pointer to the pointer
capsule * rezult = std::move(specimen);
// two equal pointers
_ASSERTE( specimen == rezult );
// do not erase twice
delete specimen;

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?

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.  Which is not a surprise if you peek into the synopsis of  the std::iter_swap :

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