Migrating from C to C++: NULL vs nullptr

On my last project, my team started working on implementing object-oriented concepts in C. Eventually we realized that we were reimplementing multiple C++ features. We admitted to ourselves that we would not implement our constructs more efficiently than the C++ compiler.

We were also very interested in the extra safety mechanisms implicit in C++: better type safety, smart pointers, nullptr. We decided to jump in and give C++ a shot. I won't lie and say it was easy. There are a lot of C habits that are hard to break, and it can be daunting to identify what features are actually useful to learn.

I am starting a series to help guide C programmers who are interested in using C++ into an easier transition. I aim to highlight useful features and specific behavioral changes for embedded C developers.

Today we'll tackle an easy first topic: the nullptr keyword.

nullptr

nullptr is a new keyword introduced in C++11. nullptr is meant as a replacement to NULL. nullptr provides a typesafe pointer value representing an empty (null) pointer.

The general rule of thumb that I recommend is that you should start using nullptr whenever you would have used NULL in the past.

NULL

What's so wrong with our old friend NULL, anyway?

Traditionally, the NULL macro is an implementation defined constant representing a null pointer, usually the integer 0. In C, the NULL macro can have type void *. However, in C++ this definition is invalid, as there is no implicit cast from a void * type to any other pointer type (which C allows).

Since C++11, NULL can be either an integer literal with value zero, or a prvalue of type std::nullptr_t. Because of this ambiguity, I recommend switching exclusively to nullptr. To quote cppreference with a code example:

//Possible implementation
#define NULL 0
//since C++11
#define NULL nullptr

NULL vs nullptr

Let's consider the following initialization statements:

int* x = nullptr;
int* y = NULL;
int* z = 0;

Interestingly, this code produces the same results. Quoting from cppreference:

A null pointer constant may be implicitly converted to any pointer type; such conversion results in the null pointer value of that type. If a null pointer constant has integer type, it may be converted to a prvalue of type std::nullptr_t.

If functionally they are the same for pointer assignments, what's so important about distinguishing the two?

Types & Function Overloading

Recall our emphasis on NULL (0) being defined as an int literal value. C++ supports better type checking and function overloading, so the type distinction is important.

The example case below is not a common use case, but it demonstrates the dangers of NULL being defined as an int. Consider the following two overloaded function prototypes for f:

int f(int x);
int f(int * p);

If you invoke f(NULL), you will enter the f(int) function rather than the f(int *) function like you intended. The solution to such problems is to use f(nullptr) instead.

Templating

Another benefit of using nullptr is that you can specialize some of your templates for nullptr cases.

Since the type of nullptr is std::nullptr_t, you can do something like this to handle nullptr as a specific case:

template<typename T>
void foo(T * p); // template based on non-nullptr types
void foo(std::nullptr_t); //specialized overload for nullptr value

Putting it All Together

As I mentioned above, the general rule of thumb that I recommend is that you should start using nullptr whenever you would have used NULL in the past.

As a reminder, since C++11, NULL can be either an integer literal with value zero, or a prvalue of type std::nullptr_t. Because of this ambiguity, I recommend switching exclusively to nullptr.

nullptr will make your code less error-prone than relying on the implementation-defined NULL. But your code will not blow up if you stick to NULL. Your existing code is probably fine to leave as it is.

Further Reading