Migrating to Modern C++

When moving from C or early C++ to C++11/C++14, it can be difficult knowing where to start. Here are some minor features to focus on that will improve your programming and make your life easier.

NULL vs nullptr

I'll just go ahead and say it: eliminate NULL from your vocabulary right now. nullptr is the way forward and provides a typesafe value for you to use with your pointers.

NULL and 0 are int type. nullptr was added to represent an explicit pointer type.

Since functions can be overloaded, using NULL instead of nullptr could result in the wrong prototype being used. Consider the following case:

int foo(int x);
int foo(int * x);

With these two function calls:


The first call would end up invoking foo(int) while the second call invokes foo(int*).

auto typing

Types in C++ can be very verbose, especially once templating is involved. The auto type keyword can be used to infer the proper type.

Let's consider a std::vector:

std::vector<int> v;

I'll often see something like this when reviewing code, which isn't correct:

uint32_t sz = v.size();

The official return type of the std::vector size() function is actually std::vector<int>::size_type. By picking uint32_t above, there's now potential for a 64-bit to 32-bit conversion. I bet that behavior is probably not what the author intended! Instead, auto comes to your rescue to prevent you from having to remember the full type.

auto sz = v.size(); //correct type

Or take this shared pointer example:

//Equivalent functionality
std::shared_ptr<uint32_t> ptr(new uint32_t(0xffbbeeaa));
auto ptr(std::make_shared<uint32_t>(0xffbbeeaa));

Using auto in your code will help save typing, free you to remember more useful details, and improve type safety.

Better Initialization

There are lots of ways to initialize values and objects in C++11/14:

int x(0);
int y = 0;
int z{0};
int z = {0};

In the above examples, the braces represent what C++11 calls "uniform initialization":

To address the confusion of multiple initialization syntaxes, as well as the fact that they don’t cover all initialization scenarios, C++11 introduces uniform initialization: a single initialization syntax that can, at least in concept, be used anywhere and express everything.

This uniform initialization syntax now allows the following, which was not possible before C++11:

std::vector<int> v{1, 3, 5};

override keyword

Declare all virtual functions that you plan to override in derived classes with the override keyword:

virtual int foo(char x) override;

This will tell the compiler to complain if you are not overriding a function but defining a new one instead. Goodbye virtual headaches!

Scoped Enumerations

When using enumerations, you can run into naming issues pretty easily:

enum Color {black, white, red};
auto white = false; //error - white already in scope

Now you're able to scope your enums within a class!

enum class Color {black, white, red};
auto white = false; //Color::white scoped under Color
auto color = Color::white;

You can also specify the underlying type of the enumeration:

enum class Color: uint8_t {black, white, red}

Further Reading