Move Semantics in C++11 (Part 2 of 2)

Last week we talked about the problem with passing objects around in the 2003 C++ standard, and mentioned how the new move semantics in C++11 could help us avoid expensive object copies.

In this article I continue where I left off last week and show an updated version of the code that implements move semantics.

Now, the new code might be generated by a C++11 compiler automatically, just like default copy and assignment operators are generated, but I will show you how to implement these yourself.

Normally, we would just go about adding the new functions to our program, and that’s the way it should be for new C++11 codebases. Chances are, however, that today you will probably be adding support to an existing project that might need to be built for a platform that doesn’t have a readily available C++11 compiler.

You wouldn’t want to break compatibility with that platform, so something you can do is to conditionally compile move semantics into your program. This way, you can have the best of both worlds: a speedy implementation for programs compiled with a state-of-the-art compiler and a (slower, but working) fallback for all other target platforms.

So, bearing this in mind, let’s add a new move constructor and a new move assignment operator to A, but protected by a MOVE_CTOR macro:

#include <iostream>
using std::cout;
using std::endl;

class A
{
	public:
		A();
		A(const A& other);
		A& operator=(const A& other);

#ifdef MOVE_CTOR
		A(A&& other);
		A& operator=(A&& other);
#endif //MOVE_CTOR

		float _a;
};

A::A() : _a(0.0f)
{
	cout << "Running A()" << endl;
}

A::A(const A& other)
{
	cout << "Running A(const A&)" << endl;
	_a = other._a;
}

A& A::operator=(const A& other)
{
	cout << "Running operator=(const A&)" << endl;
	_a = other._a;
	return *this;
}

#ifdef MOVE_CTOR

A::A(A&& other)
{
	cout << "Move-constructing A" << endl;
	_a = other._a;
	other._a = 0.0f;
}

A& A::operator=(A&& other)
{
	if (&other != this)
	{
		cout << "Move-assigning A" << endl;
		_a = other._a;
		other._a = 0.0f;
	}

	return *this;
}

#endif //MOVE_CTOR

A getAnA()
{
	A a;
	a._a = 1.0f;
	return a;
}

int main(int argc, char* argv[])
{
	cout << "==Declaring A a==" << endl;
	A a; 
	cout << "==Assigning a=getAnA()==" << endl;
	a = getAnA();
	cout << "==done==" << endl;
	cout << "a._a = "<< a._a << endl;
	return 0;
}

To build this source file with support for move semantics, use the following command. Older compilers can opt-out of building C++11-specific code.

clang++ -std=c++11 -stdlib=libc++ -DMOVE_CTOR main.cpp

If everything's alright, here's the output that should be produced from running this program:

:@~/devel/C++/move_ctor11$ ./a.out 
==Declaring A a==
Running A()
==Assigning a=getAnA()==
Running A()
Move-assigning A
==done==
a._a = 1

Here we can immediately notice how we are avoiding running the (expensive) copy assignment operator and we substitute it with a lightweight move assignment that takes the internal data of the source object.

Think how this could help you avoid deep-copying matrices, trees, memory pages, and even how it would help you code safer by preventing having to sacrifice const-correctness and immutable object state in your code as hacky ways to gain performance. C++11 really does feel like a new language.

Stay tuned for more C++11 goodness next week!