Move Semantics

One of the new features in C++0x is a way to express move semantics. This is a sensible piece of sibling functionality to copy semantics, which you’ve likely already run into. When writing copy semantics for a class, the idea is to take data from the right-hand object and copy it into the left-hand object. You’ve seen this in many forms, such as a copy constructor, assignment operator or even a Clone method. Move semantics are similar, except instead of copying the data from the right-hand object, you move it from the right-hand object into the left-hand object.

The problem with copy semantics is that they sometimes solve the wrong problem. Consider the following code:

class Tester {
private:
	void *mData;
	int mSize;

public:
	Tester() : mData( NULL ), mSize( 0 ) {}
	Tester( int size ) : mData( NULL ), mSize( size ) {
		if (mSize > 0)	mData = ::malloc( mSize );
	}

	Tester( const Tester& t ) {
		::printf( "copying\n" );
		*this = t;
	}

	virtual ~Tester() { 
		::free( mData );
	}

	const Tester& operator= ( const Tester& t ) { 
		::printf( "copying assignment\n" );
		mSize = t.mSize;
		if (mSize) {
			mData = ::malloc( mSize );
			if (mData) {
				::memcpy( mData, t.mData, mSize );
			}
		}
		return *this;
	}
};

Tester GetIt()
{
	Tester t( 12 );
	return t;
}

int main( void ) {
	Tester v = GetIt();
	return 0;
}

The function GetIt allocates a local variable (t) of type Tester on the stack by calling Tester::Tester( int ), and then it returns it. The act of returning the stack-based variable means that the data must be copied into a temporary variable (in this case, it’s the variable v within main) so that its scope exists outside of the GetIt method. So Tester::Tester( const Tester& ) is called to copy t into a temporary variable, and then Tester::~Tester() is called to destroy t. In this fashion, the data from GetIt flows into the storage within main.

However, this means you have to have twice the amount of memory used for Tester::mData. One copy for the local variable t, and one copy for the v variable. As you can imagine, if Tester::mData was a large buffer, this could be wildly inefficient. What’s more, there’s no need for it to be inefficient! We know that t is going to be destroyed anyways, so why not just “move” t::mData into v::mData. This would eliminate the need for a duplicate buffer in memory and an expensive memcpy as well.

That’s move semantics in a nutshell. Just like writing a copy constructor or an assignment operator, you are able to write a move constructor or move assignment operator. These move operations use the && operator to designate that the parameter is an “rvalue reference.” The basic idea behind the operation is that you “move” the data from the right-hand side to the left-hand side, and then destroy the right-hand side in such a way that its destructor won’t malfunction. For instance, with our Tester class, we’d add:

Tester( Tester&& t ) { 
	::printf( "moving\n" );
	*this = std::move( t );
}

const Tester& operator= ( Tester&& t ) { 
	::printf( "moving assignment\n" );

	// We want to swap our data with t -- this way, we get
	// all of t's data cheaply, and t (which presumably will
	// be destroyed soon) will handle free'ing our data.
	std::swap( mSize, t.mSize );
	std::swap( mData, t.mData );

	return *this; 
}

The operator= function shows the basic concept behind a move operation. The data is migrated from the right-hand side to the left, and then the right-hand side is modified in such a way that the move is “complete.” That is why the move operations accept a non-const parameter — so the right-hand side can be modified. Since we are still responsible for destroying any data that may be allocated to the left-hand side object, we simply move it into the right-hand side. It’s a big swap function!

There are a lot of nuances to rvalue references and move semantics which make them slightly more esoteric when compared to lvalue references. Because of this, it’s likely a construct that will be used mostly by framework designers instead of by everyday coders. However, understanding that this feature exists and how to use it will still help those in the trenches to write efficient code.

This entry was posted in C/C++ and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.