Rvalues in move constructors and move-assignment functions

C++11’s Rvalues have been covered pretty well in various articles, books and Q&A boards. Unfortunately, there are still common mistakes made with respect to their use in move constructors and move-assignment functions.  So I just thought I’d focus a little time on showing what can go wrong when Rvalue references are misused and how to avoid it.  If you haven’t read up on them, or don’t fully understand what Rvalues are, you must look at these resources:

Note that I’ve included links to info on universal references and reference collapsing because this is probably an even bigger source of confusion.  You may not actually encounter this problem if you don’t use template functions all that often, but its very important to know that Rvalue reference parameters can really become Lvalue references in template functions, and of course this is why std::forward() exists (perfect forwarding).

Move-function Declarations

Okay, with that material behind us, let’s examine now how move constructor and move-assignment functions look in a typical class:

   // Move constructor
   Object(Object&&);
   // Move-assignment operator
   Object& operator=(Object&&) noexcept;

These two functions look pretty much like their copy siblings, with the exception that there is no const specifier and we are of course using two ampersands.  The noexcept specifier is also a new C++11 addition, and its use in move-assignment functions is optional – however, without it some compilers may opt not use it in areas (like STL containers) where move functions are expected not to throw.  If you have a compiler which doesn’t support noexcept, I’d suggest using a ‘NO_EXCEPT‘ type macro which perhaps uses the old throw() modifier on non-conforming compilers.

Now, with the declaration out of the way, it’s time to move onto the definition of the functions..

A basic move constructor

A move constructor is fairly simple in that we are basically initializing everything with moves:

   // MOVE constructor
   Object(Object&& moveFrom) :
     m_Data(std::move(moveFrom.m_Data)),
     m_Val(std::move(moveFrom.m_Val))
   {
   // POD types can simply be copied, then invalidated
      m_ValidFlag = std::move(moveFrom.m_ValidFlag);
      m_NativeType = std::move(moveFrom.m_NativeType);
      moveFrom.m_ValidFlag = false;
      moveFrom.m_NativeType = 0;
   }

The above code demonstrates a few things:

  • 1st, the member initialization list is used outside of the function body.  This ensures that we call the right object constructors before the function body.  If we initialize an object inside the constructor body, then we could easily wind up reinitializing an object.  Certain compilers will optimize the first initialization out, but in practice you should always always initialize objects in the initialization list.
  • 2nd, every time the Rvalue reference is used in any expression, it is surrounded by std::move(). This is where a lot of programmer’s make mistakes initially (including me!).  You need to literally spam your move functions with std::move() every time the Rvalue reference is used – either as a whole, or when used to reference individual member variables.

    The reason for all the calls to std::move() is of course the fact that Rvalue references behave like Lvalues in expressions – this is covered in Thomas Becker’s article, and can be most easily remembered as ‘named variables are Lvalues‘ (or at least, act like them). std::move() is really just a one-line Rvalue-reference cast, so it comes at no cost.

  • 3rd: Although this is optional, its a good idea to use std::move() even on native data types and POD (plain old data) types, to keep things uniform and consistent.  You’ll still need to manually invalidate this data in the Rvalue object (as needed), however.

A basic move-assignment function

A move-assignment function is a bit more complicated than the move-constructor.  Here we know ahead of time that the object has already been initialized, so we need to cleanup or destroy the object’s current resources before we replace it with the new data.  This is more or less the same behavior as a copy-assignment function, but of course here we are ‘consuming’ the passed object’s data:

   // MOVE-assignment operator
   Object& operator=(Object&& moveFrom) noexcept
   {
   // Avoid copies to self (same as for COPY-assignment)
      if (this != &moveFrom)
      {
         // Clean up our resources first
         destroy_this();

         // MOVE object members
         m_Data = std::move(moveFrom.m_Data);
         m_Val = std::move(moveFrom.m_Val);

         // POD types can simply be copied, then invalidated
         m_ValidFlag = std::move(moveFrom.m_ValidFlag);
         m_NativeType = std::move(moveFrom.m_NativeType);
         moveFrom.m_ValidFlag = false;
         moveFrom.m_NativeType = 0;
      }
      return *this;
   }

Just as a copy-assignment function has a check for self-assignment, so too does a move-assignment function – although the chances of it happening are pretty slim.  Once it is determined that this function isn’t a self-assignment, the first thing it does is of course destroy the current resources (destroy_this();).  After that, we can then move-assign the internal objects with the Rvalue object’s members, and then work on the POD types.  We could as well call some internal Rvalue function inside each object to do the moves, but here we assume that each object has its own move-assignment function, or at the very least a copy-assignment function.

Make sure moves are persistent!

Things get complicated when we have to think about what objects accept Rvalues and which ones don’t.  When using the C++ STL library, every object is pretty much guaranteed to have Rvalue assignment operators and constructors, so we can safely assume that a move function will in fact be called when needed.  However, with your own objects and with other object libraries, you will need to check if this is the case.  Sometimes where move functions are lacking, there may be other special functions that do what we intend – for example, acquireObject() and release() may be move-like functions we’d need to explicitly call in such cases.  This can of course become tedious very quickly, but until everything we use is updated with move capability, you’ll need to examine object declarations closely.

Derived classes and moves

Another area I see problems occurring at with move functions is with derived classes.  We obviously want the parent object (or super class) to move right along with the derived (or subclass) object.  Assuming we built the parent object and included the necessary move constructor, the above move constructor can be simply modified with a call to the parent’s move constructor as the first item in the initialization list:

   // MOVE constructor
   Object(Object&& moveFrom) : Parent(std::move(moveFrom)),
     m_Data(std::move(moveFrom.m_Data)),
     m_Val(std::move(moveFrom.m_Val))
   {   // body - see above   }

Nothing much else to say here, other than the obvious: always use std:move()!

The move-assignment function will need to do extra work as well.  The simplest form of moving the Parent object’s data is to manually invoke a move-assignment operator, something like this: Parent::operator=(std::move(moveFrom));.  Obviously, there are many cases where that will not work, and really it boils down to how the data in the parent class interrelates with the data in the derived class.  Just remember to destroy all resources, use std::move() on every assignment, and you should be fine.

The copy/move right-vs-wrong-way example program

Okay, since I figure the best way to show the right and wrong way to implement move functions is to give an example piece of code, I’ve written one up which hopefully clarifies everything.  You can skip to the link below to see the program and its output, but I should of course cover what it is first.

In the example program, there’s 1 parent class (SuperClass), and 2 derived classes (SubClass and SubFailClass), one which acts appropriately, the other which misbehaves by incorrectly handling moves in both the constructor and assignment operator.

    SuperClass
    |       |
SubClass  SubFailClass

Inside SuperClass are 4 data members, one of which is a std::string, the other are basic types, with the exception of <T> m_Data which is a template object:

std::string m_Str;
T m_Data;
int m_Int;
bool m_bValid;

The main areas of focus in the source code are the copy and move constructors, and the copy and move assignment operators. Here’s how the base class copy/move members look:

SuperClass(const SuperClass& copyFrom) : m_Str(copyFrom.m_Str), m_Data(copyFrom.m_Data)
 {  m_Int = copyFrom.m_Int;  m_bValid = copyFrom.m_bValid; }

SuperClass(SuperClass&& moveFrom) noexcept
: m_Str(std::move(moveFrom.m_Str)), m_Data(std::move(moveFrom.m_Data))
{
   m_Int = std::move(moveFrom.m_Int);
   m_bValid = std::move(moveFrom.m_bValid);
}
SuperClass& operator=(const SuperClass& copyFrom)
{
   if (this != &copyFrom)
      assign(copyFrom);
   return *this;
}
SuperClass& operator=(SuperClass&& moveFrom) noexcept
{
   if (this != &moveFrom)
      assign(std::move(moveFrom));
   return *this;
}

As discussed, std::move() is used in the move variants to re-cast the ‘moveFrom‘ member into an Rvalue reference in each expression. Also, instead of making changes in the copy- and move-assignment functions, the assign() functions are called to make a copy or move. That function is relatively predictable for copy: just destroy the object contents and replace it with the copyFrom object:

void assign(const SuperClass& copyFrom)
{
   // Our data will be overwritten, so its best to free the resources 1st
   destroy();
   // Now we can copy
   m_Str = copyFrom.m_Str;
   m_Data = copyFrom.m_Data;
   m_Int = copyFrom.m_Int;
   m_bValid = copyFrom.m_bValid;
}

The move variant of course moves all the members, and invalidates the Rvalue object’s POD data ( a bool and int here):

void assign(SuperClass&& moveFrom)
{
   // Our data will be overwritten, so its best to free the resources 1st
   destroy();
   // move for non-POD and unknown types:
   m_Str = std::move(moveFrom.m_Str);
   m_Data = std::move(moveFrom.m_Data);

   // for POD members we can simply copy-assign, then invalidate the temporary
   m_Int = std::move(moveFrom.m_Int);
   m_bValid = std::move(moveFrom.m_bValid);

   moveFrom.m_Int = 0;
   moveFrom.m_bValid = false;
}

The derived classes SubClass and SubFailClass are basically superficial class objects. Their only purpose here is to demonstrate the correct and incorrect way to invoke move functions in the base class. The assignment operators are simple in that they call the parent class’s assign() function; however, the way they call it is what’s important. One properly invokes it using std::move(), the other fails. Also, here’s the way their constructors look:

SubClass(const SubClass& copyFrom) : Parent(copyFrom) {}
SubClass(SubClass&& moveFrom) noexcept : Parent(std::move(moveFrom)) {}

SubFailClass(const SubFailClass& copyFrom) : Parent(copyFrom) {}
SubFailClass(SubFailClass&& moveFrom) noexcept : Parent(moveFrom) {}

The latter class is the obvious failure here (as its name would indicate) – std::move() isn’t used!

The rest of the code in the example program just demonstrates how copies and moves happen through some helper functions. The functions are documented enough so that hopefully it all makes sense. Also, every function call is reported through cout, so it should make following the program’s inner workings more understandable.

Note that the output for constructors and destructors happens in reverse order. Also very important – a moved object should not contain a string at destruction. As you’ll see by the output, that’s not the case for SubFailClass, since it fails to do a proper move.

..and finally..

The example program and it’s output can be found on the online Coliru compiler.

UPDATE: Look at the output from the alternate version because the old version now doesn’t give the output we want.  The reason is the compiler now optimizes temporary returns – normally a good thing – but for demonstration purposes, doesn’t give the output we want.  The source code for the original is also available at Pastebin.

Kudos to you if you sat through that long post!  Until next time..

Advertisements

initializer_list frustrations – Solving the deep-copy issue

So I’ve messed about a bit with C++11’s initializer_list objects and thought they were a great idea.  Since I’ve created my own container, I included them to mimic the new standard functions for constructor, assignment, and insert() functions.  However, there’s one (big) flaw I find with them – for types that we might use to construct objects in a container, the objects themselves are constructed before they are placed into the initializer_list object.

So for example, say we have a container – a vector – of strings.  Nothing fancy. Now, if we’d say, want to append a group of strings at the end of the vector using string literals, we’d use something like this:

std::vector<std::string> MyStrVec;

MyStrVec.insert(MyStrVec.end(), {"str1", "str2"});

Relatively harmless, yes? So, if we consider this for a moment, the most efficient way to insert those elements into the container is to construct them in-place, or to move-construct them with temporary objects (RValues).  But why, why why did the C++11 standards committee decide, especially in the light of the significance of RValues, to make everything copy-constructed?!

What happens above is this sequence:

  1. First, “str1” and “str2” are used to construct temporary string objects
  2. The insert function gets called with an initializer_list consisting of pointers to const strings.  Note the const here – it means there is no chance of moving anything.
  3. In the insert function, the objects in the initializer_list are traversed and inserted into the vector one by one using copy construction
  4. Following the call, the temporary string objects are destroyed.

So, even though a huge aspect of the C++11 standard has been focused on minimizing wasteful copies by using RValues (moves) and in-place construction (see emplace, which uses variadic templates), here we have initializer_list’s fouling everything up.  By using this new convenient feature, we just went where C++11 generally tries not to take us – the deep-copy route.  In the above code, the temporary string objects allocate memory and initialize their members with strings, then the insert code constructs new string objects which also allocate and initialize their data with strings (using copying).  Therefore by using that initializer_list object we basically doubled the amount of memory (and time) needed to insert strings into a vector. W. T. F. right?

This is all very vexing, and perplexing.  Why waste memory and time when we have much better means of addressing the situation?  Meh, there’s nothing we can do – its now-standard.  However!  Having said all that, this doesn’t preclude us from creating our own workarounds, which despite my negative feelings towards them, use initializer_lists.  How so?  By using a proxy function of course!

Now, keep in mind that initializer_lists can only take an object of a single type – this is very different than variadic templates, which can take any number of different types.  In fact, that could be one way to solve the problem of inserting items initialized by different object types, but it gets annoying using recursion functions.  Plus, you’re still limited to one parameter per constructed object.  So, no, for me I find it much better to focus on a simple initializer_list proxy function.

So given the above code, and the concept that we want to not waste any time or memory, we can create a function like such:

template <typename VecT, typename Iterator, typename LType>
void insertInitListProxy(std::vector<VecT> & Cnt, Iterator pos, std::initializer_list<LType> il)
{
   // save iterator 'distance' from begin() in case a resize operation happens
   auto diff = pos - Cnt.begin();
   // pre-allocate space
   Cnt.reserve(Cnt.size() + il.size());
   // reset iterator based on any changes that occurred due to a resize
   // from here on out, we don't need to worry about resizes
   pos = Cnt.begin() + diff;
   // construct the items in-place
   for (auto it = il.begin(); it < il.end(); ++it)
   {
      pos = Cnt.emplace(pos, *it);
      ++pos;  // move to position *after* what was just inserted
   }
}

Simple enough, yes?  We allocate the space and construct the items in-place using whatever type ‘LType’ is – in our case, ‘const char*’ strings. There’s some management code in there to deal with resizes causing iterators to be invalidated, which is why we need to calculate the offset of the iterator based on the vector beginning. The vector may change location in memory, so we save that offset, then change it back into a normal iterator after the resize. So, given the above, here’s what the call would look like now:

insertInitListProxy(myVec, myVec.end(), {"str1", "str2"});

That’s not quite as convenient, but its close enough!  And it wastes no memory or time. “str1” and “str2” are passed as pointers and then constructed in-place in the vector.  So there’s my proposed workaround =)  However, keep in mind that all the objects in those curly braces must be of the same type, or promotable to a common type.  If not, you’ll either need to use casts or somesuch to let the compiler know that these are all the same type. (Or just explicitly specify the template parameters).  Oh, I might as well include a more generic container version for everything other than vectors:

template <typename Container, typename Iterator, typename LType>
void insertInitListProxy(Container & Cnt, Iterator pos, std::initializer_list<LType> il)
{
   for (auto it = il.begin(); it < il.end(); ++it)
   {
      pos = Cnt.emplace(pos, *it);
      ++pos;  // move to position *after* what was just inserted
   }
}

That last one should probably appear before the vector-specific one in your source code, so as not to confuse the compiler.  Note that you could in fact use just the latter one (which is admittedly simpler), but you can’t actually call the reserve() function to optimize things even further.

Since it might be hard to see what is going on without a complete example, I’ve created one at Coliru – the Initializer_List Proxy Test!  (also available at PasteBin) You’ll be able to see all the copy-construction going on after the “Now invoking initializer_list insertion” line. which should be contrasted with what happens after the “Now invoking initializer_list proxy” line.

Anyway, hope that helps someone.  If not, it helped me!

*edit: Note: an insert_iterator would be the preferred way to insert items in a list, but emplace here increases the container size by 1 and returns an iterator to the inserted element, so its safe to use ++pos to index the next location to insert (i.e. its legal to insert at end()).

*edit2: its important to note it’s not safe to write ‘pos + 1‘ as it was originally, since that will only work with random access iterators! (oops – that’s what I get for focusing on vectors). ++pos is guaranteed to work though.