What Features I Like the Most in C++11 (Part 1)

I recently received some feedback from a colleague that my posts have made the assumption that readers understand C++ features added by the C++11 standard. Because I agree with this observation I want to take a brief step back and look at some C++11 features which have been very useful to me and the projects I work on. As I was writing this first post it started to become too long, so this will end up being a series of posts.

Instead of hashing out an entire survey of C++11’s new features over <= C++03, I think you should start with this blog post. It doesn’t waste too much time on any one feature, getting to the point with each one. It is not comprehensive, but maybe helpful if you are completely new to C++11 but have existing C++ experience.

C++11 added an overwhelmingly large number of changes to the language which have fundamentally changed the way C++ is written and reasoned about, give us ways of writing simpler, more consistent code. In fact the way C++ is written differs so much from previous standards that it feels like a new language, where the C++ community has coined the term “modern C++” to refer to the new found language paradigm.

You may have some preconceptions or doubts about a “different C++”, but I think it is most valuable to approach the topic with an open mind and consider why features were added to the language, not focusing on the number of features. It is hard to believe that adding features makes programming simpler, but I think if you understand the reason the features exist you will find yourself reaching for a better tool in the C++ toolbox when encountering some common programming problems.

Therefore I want this series of posts to be an overview of the language (and some library) features which C++11 added to make our lives as C++ programmers a bit better. My list consists of “my favorites”and is definitely not comprehensive. In other words, my list touches more on things I use most often and should not be interpreted as being more important or inherently better to use! It’s also important to note that C++14 and upcoming C++17 standards also add even more useful features (not nearly as many as C++11 did), but my day-to-day job requires compatibility with older compilers so C++11 remains my focus for the time being.

Lastly, these are not listed in any particular order, rather they are in the order they came to mind. So without further ado…

New ‘using’ type aliases

We all have seen typedefs all over the place in code we have worked on. They give us a way of telling the compiler that a type name actually refers to another known type. We get ‘typedef’ inherited from C, but C++11 added a new way of spelling it. Aliases specified with ‘using’ try to unify the way we read declarations for both compile-time types and run-time variables. Take the following declaration:

int x = 10;

This takes a declared variable on the left-hand side and assigns it the value on the right-hand side: a very common declaration syntax. However, typedefs change this ordering which creates an inconsistency. For example:

typedef int IntAlias;

…which gets worse if you start adding pointers or arrays to them:

typedef int IntMatrix[3][3][3];

The ‘using’ syntax for this makes this type renaming look more like a regular assignment, where IntMatrix would be defined as:

using IntMatrix = int[3][3][3];

The new ‘using’ alias can also be templatized (something you cannot do with a typedef), if it makes sense to do so:

template <typename T, int SIZE>
using Matrix = T[SIZE][SIZE][SIZE];

using IntMatrix3 = Matrix<int, 3>;

Lambda functions

In a previous post I have used lambda functions in examples and used the term “closure”. My very naive use of the term “closure” refers to “some function or callable object which completes a particular operation”. Another synonym for this definition is predicate, if that is helpful in understanding what I mean. Please don’t beat me up too much if I’m not using the purest of mathematical terms here!

Lambdas are a way of inlining a class which implements operator() and optionally captures any state where it is defined. There are 3 parts to a lambda:

  1. Capture region – []
  2. Argument list – ()
  3. Lambda body – {}

The argument list and lambda body are like any function or operator() in a class, so that should be straightforward to understand. The “magic” of lambdas is in the capture region. In most cases, you’ll probably find yourself writing ‘[]’, ‘[&]’, or ‘[=]’. In the case of ‘[&]’, any variables which are referenced inside the lambda body (which are valid in the scope which the lambda is defined) are taken by reference, and ‘[=]’ means they are copied by value. Take the following example

std::vector<int> myVector = {0, 1, 2, 3, 4, 5};

int threshold = 2;

int num = std::count_if(myVector.begin(),
                        [&](int v) { return v > threshold; });

This would count the number of values in the vector which are greater than the value of “threshold”. The lambda took the variable “threshold” by reference and used it inside the implementation of std::count_if(), where it calls the lambda on each item in the vector. Even though the example is contrived, hopefully you see the value of being able to inline an entire class definition in an exact context.

Lambdas also have a code clarity benefit: the function object is defined exactly where it is used, minimizing the amount of code outside this scope you must know about. To achieve the same concept in previous C++ standards you would have had to write an entire class to do what the lambda is doing. In other words, if I read some class definition somewhere in the code I additionally have to look at its usage to “discover” that it is a single-use class, where I am forced to remember that fact (or face going through the discovery process all over again) the next time I visit the code.

I will also point out that if a lambda doesn’t capture anything (i.e. it uses an empty capture region with ‘[]’), then the lambda is nothing more than an ordinary function. There is no inherent cost to using lambdas: they are merely a new syntactic construct. Furthermore, because lambdas are built into the core language, the compiler can very aggressively do optimizations on your behalf, such as inlining the lambda body or eliding variable storage.

Checkout my post on writing a parallel-for wrapper function for a more “real world” example.

Furthermore, more complete documentation of lambdas can be found on my favorite C++ site: cppreference.com.

Move semantics

The addition of move semantics makes it much easier to write more efficient code, but it can be a bit confusing to understand. The whole concept centers around the concept of lvalue (&) and rvalue (&&) references.

What was originally just a “reference” we can think of as an lvalue reference. The way I think about it is that a lvalue reference is a reference to an object which is “alive and well”. On the contrary, a rvalue reference is a reference to an object which is “about to be destroyed”. The most common place rvalues come up are in temporary variables, but they can come up in other scenarios as well.

Working with lvalue and rvalue references makes the most sense when you are writing a class that has some sort of resources associated with it. For example, std::vector is basically a wrapped dynamically allocated array, where the vector itself owns a region of memory referred to by some sort of pointer. If I create a temporary std::vector and assign it to another std::vector instance, it is safe for the destination std::vector to “steal” the memory from the temporary instance because the temporary instance is “about to be destroyed”. Note that if you do a move on POD types (i.e. built-in types or simple structs), a move is the same as a copy because there are no resources to move, only the data itself.

Making types movable is done the same way you make types copyable: implement a move constructor. Just like copy constructors, move constructors are created for by the compiler, but need special implementations when dealing with special resource management. Because you can also remove copy/move constructors from consideration when a type is used, you can make the decision on whether a type is “move only” or “copy only”: you are free to decide which constructors to include or remove depending on the intended usage of the type you are writing.

If you are interested in a deeper dive on move semantics, have a look at this blog post from cprogramming.com.

Type deduced declarations

Template type deduction should be familiar to C++ programmers, at least at a high level. Consider the simple case:

template <typename T>
someFunction(T value);

We know that if we write ‘someFunction(5)’, that T is deduced to be of type “int” where “value” is 5. C++11 gives us this power now in more places with two features: auto and decltype().

The new ‘auto’ keyword lets programmers declare a variable with the resulting type of an expression which initializes it. Consider the following example:

auto val = x + 5;

In this case, ‘val’ is whatever type results ‘x + 5’. I intentionally left out the type of ‘x’ because I wanted to point out a distinct advantage of using ‘auto’: if the type of ‘x’ were to ever change, the type of ‘val’ would also change to match this without any further modifications. There are other reasons to prefer ‘auto’, such as when you need to declare an iterator. While the specific type is known at compile time, I am certain that it is not important to know the exact spelling of the type returned by std::vector::begin(). Regardless, thinking of ‘auto’  type deduction like template type deduction will make it seem less mysterious.

Now some of you may be skeptical in thinking that ‘auto’ is lazy, but it has real advantages beyond it changing the style of variable declaration. First is what I already mentioned above, declared variables will track type changes which occur when other variable types change. Second, ‘auto’ requires initialization which means it is impossible to have an uninitialized variable created with ‘auto’. Third, it creates far less visual noise in certain situations as ‘auto’ lets you avoid repeating yourself. Consider the following examples:

std::shared_ptr<std::map<std::string, int>> myMap =
std::make_shared<std::map<std::string, int>>();
float *myArray = new float[10];
const float &myRef = myArray[5];

All of these declarations end up repeating types unnecessarily. Instead, they could be written with ‘auto’ as:

auto myMap = std::make_shared<std::map<std::string, int>>();
auto *myArray = new float[10];
const auto &myRef = myArray[5];

I argue that the second version of each declaration is not only easier to read, it is also more type safe. In this view of variable declaration, the left-hand side becomes much more consistent to read no matter how complicated the resulting type is on the right-hand side of the initialization. You will also notice that ‘auto’ also lets you be specific with const and pointer/reference qualifiers, if necessary. In the example ‘myRef’ is as const reference to a value in ‘myArray’, where omitting ‘const’ and ‘&’ would have declared a non-const copy of the value from the array.

C++11 also introduced ‘decltype()’, which allows you to get the type of a variable or expression at compile time. This is more useful inside templates and when dealing with ‘auto’ as it lets you instantiate an inferred type at compile time from another existing type. I end up using decltype() way less often than ‘auto’, but it has its own little niche in the C++ toolbox.

Correctly typed NULL pointer representation

Pre-C++11 code relied on the ‘NULL’ macro inherited from C to identify a null pointer value. The major problem with this is that ‘NULL’ is ubiquitously mapped to the integer value 0, which is not a pointer type! Thus code which uses the ‘NULL’ macro relies on implicit conversion to and from ‘int’ to pointer, which can have subtle problems and sometimes upset compilers. To fix this, C++11 introduced the keyword ‘nullptr’, which by definition is a literal pointer type.

I have yet to find a case where substituting ‘nullptr’ for ‘NULL’ did not improve code. Thus I believe C++11 projects should always do that substitution and use ‘nullptr’ moving forward. I do not prefer absolute advice, but this one is hard to argue the other way in my opinion.

Final thoughts

I hope these new C++ features are viewed as useful and provide real benefits to the way you read and reason about code. I could probably write an entire post about the nuances and advantages of each feature in-depth, but other people have done that much better than me.

For those interested in some good books on modern C++, here are some I’ve enjoyed reading:

Also, the following are a few talks that I highly recommend watching on modern C++:

Until next time, happy coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s