The ‘Move Constructor’ in Visual C++ 2010
A new feature in Visual C++ 2010 is called Rvalue References. This is a feature from the C++0x standard. One thing that Rvalue References can be used for is to implement move semantics for objects. To add move semantics to a class, we need to implement a move constructor and a move assignment operator (optional). This article will briefly explain the benefits of move constructors and how to write them.
For my example I will first define a class called CSomeObject that does not implement any move semantics.
class CSomeObject { public: CSomeObject() : m_iBufferSize(0) , m_pBuffer(NULL) , m_iInstanceID(ms_iInstanceCounter++) { } // Normal constructor CSomeObject(unsigned int iBufferSize) : m_iBufferSize(iBufferSize) , m_pBuffer(NULL) , m_iInstanceID(ms_iInstanceCounter++) { if (m_iBufferSize > 0) { m_pBuffer = new char[m_iBufferSize]; memset(m_pBuffer, 0, m_iBufferSize); } } // Copy constructor CSomeObject(const CSomeObject& objSource) : m_iInstanceID(ms_iInstanceCounter++) { m_iBufferSize = objSource.m_iBufferSize; if (m_iBufferSize > 0) { m_pBuffer = new char[m_iBufferSize]; memset(m_pBuffer, 0, m_iBufferSize); } } ~CSomeObject() { if (m_pBuffer) delete [] m_pBuffer; m_pBuffer = NULL; } protected: unsigned m_iBufferSize; char* m_pBuffer; int m_iInstanceID; static int ms_iInstanceCounter; }; int CSomeObject::ms_iInstanceCounter = 0;
Next, I will derive a new class from the above class that will implement a move constructor.
class CSomeObjectWithMoveConstructor : public CSomeObject
{
public:
CSomeObjectWithMoveConstructor()
: CSomeObject()
{
}
// Normal constructor
CSomeObjectWithMoveConstructor(unsigned int iBufferSize)
: CSomeObject(iBufferSize)
{
}
// Copy constructor
CSomeObjectWithMoveConstructor(const CSomeObjectWithMoveConstructor& objSource)
: CSomeObject(objSource)
{
}
// Move constructor
CSomeObjectWithMoveConstructor(CSomeObjectWithMoveConstructor&& objSource)
{
m_iBufferSize = objSource.m_iBufferSize;
m_pBuffer = objSource.m_pBuffer;
objSource.m_iBufferSize = 0;
objSource.m_pBuffer = NULL;
}
};
The move constructor (red code) first copies the member variables from the source object to the new object and then sets the variables of the source object to null values. By doing this, the move constructor is actually moving the memory from m_pBuffer from the source object to the new object. Then it set the m_pBuffer pointer to NULL for the source object to prevent the destructor of the source object to deallocate that memory, because now the new object is the owner of that memory.
Note that you use && to declare a Rvalue reference.
The Visual C++ 2010 STL implementation has been updated to use move constructors for objects whenever they are available in a class. For example, the std::vector will automatically use the move constructor when increasing the size of the vector. Previously, the following steps were required for increasing the size of a vector:
- A new vector with the correct size is allocated.
- All objects from the old vector are copied to the new vector.
- All objects in the old vector are destroyed.
- The old vector is deallocated.
With the move constructor it looks as follows:
- A new vector with the correct size is allocated.
- All objects from the old vector are moved to the new vector.
- All objects in the old vector are destroyed.
- The old vector is deallocated.
There doesn’t seem to be much differences between the two flows. The only thing different is step 2. However, when for example your objects allocate a lot of dynamic memory as in my example class CSomeObject, then the act of copying objects in step 2 can be quite expensive. Using the move constructor, that step becomes much faster as it avoids the dynamic allocation of those buffers. Take the following two loops for example:
static int siBufferSize = 1024*1024*10; static int siObjectCount = 100; // First without move constructor vector<CSomeObject> vec2; for (int i=0; i<siObjectCount; ++i) { vec2.push_back(CSomeObject(siBufferSize)); } // Now with move constructor vector<CSomeObjectWithMoveConstructor> vec3; for (int i=0; i<siObjectCount; ++i) { vec3.push_back(CSomeObjectWithMoveConstructor(siBufferSize)); }
On my test laptop, the first loop takes around 2600 milliseconds, while the second loop using move constructors takes only 530 milliseconds.
Obviously, move constructors are only useful when you know that the original object will be destroyed, as is the case for the vector example above. It can be used anywhere where temporary objects are involved. For example:
string str = string("hello ") + "world";
Without move constructors, the + operator allocates and returns a new temporary string. With move constructors this object copying is avoided.
Learn more about Rvalue References in Visual C++ 2010.
The Visual Studio 2010 project below is a demo application of using move constructors and the performance benefits of using them.
Julian said,
Wrote on July 15, 2010 @ 12:17 am
I think that objSource.m_iBufferSize = 0;
is not required; it is axiomatic that the only thing left for objSource to do is to die quietly.
Whilst it is not wrong it could engender a whole field of misunderstanding!
Marc Gregoire said,
Wrote on July 15, 2010 @ 9:51 am
I don’t agree. It’s good practise to reset variables to safe values after doing something with them.
For example, after deleting a pointer, it’s good to put that pointer back to NULL, even if the object is in the process of dying. It can make debugging for example easier.
air2 said,
Wrote on June 10, 2011 @ 12:58 pm
I agree with Marc, although it is only interesting for debugging, in release mode, modern compilers will remove the instructions anyway due to optimalisation, because the see that the variable is not read after setting them. So it will not have bad performance effects what so ever. Therefore I see it as good code practice.
Andrii said,
Wrote on October 7, 2011 @ 4:40 pm
and if it will be
// Now with move constructor
vector vec3;
for (int i=0; i<siObjectCount; ++i)
{
CSomeObjectWithMoveConstructor myobj(siBufferSize);
vec3.push_back(myobj);
}
copy constructor will be called?
Marc Gregoire said,
Wrote on October 9, 2011 @ 8:54 am
Yes, in that case the copy constructor will be called, because a named object (myobj) will never be bound to an rvalue reference.
Neo said,
Wrote on January 29, 2013 @ 10:39 am
Hey Marc, Thanks for wonderful explanation, I need a clarification though ?
You mentioned – The Visual C++ 2010 STL implementation has been updated to use move constructors for objects…
In case our classes does not have move constructors and they are objects are stored in STL containers like vector, would it still leverage the move semantics performance( as STL containers are already move enabled) for all operations like increasing size of vector, sorting …etc
Marc Gregoire said,
Wrote on January 29, 2013 @ 8:43 pm
Neo: The VC++ 2010 STL implementation does indeed support move semantics for things like vectors. That means that returning a vector by value from a function is very efficient, since it will be moved.
The STL containers can also use move semantics on the objects stored inside the container, but only in the case those objects are moveable. In other words, if you make your objects moveable, then the STL containers will use move semantics on them for operations such as increasing the size of the vector.