Modern C++ Features – nullptr

Probably everybody who has written C++03 code had the pleasure of using NULL and stumbling over one pitfall or another. C++11 brought the solution to those issues with nullptr.

What is NULL?

That question is not answered too easily. NULL is a macro that can be defined as 0 (i.e. the integer zero) or 0L (zero long int) or some other null pointer constant, but it will basically be a constant of integral type. Null pointer constants are called that way because they can be converted to null pointers. In C NULL could also be (void*) 0 since void pointers were implicitly convertible to any other pointer type.

Houston, we have a NULL!

NULL is clearly meant to be used for pointers. However, if you use it with function parameters, it will behave as whatever it is defined to be, i.e. an int or long int, and not as a pointer. Consider these examples:

class Spell { /* ... */ };

void castSpell(Spell* theSpell);
void castSpell(int spellID);

int main() {
  castSpell(NULL); //casting without an actual spell
}

From reading the code, we would surely expect and want the first overload to be called, with a null pointer. Sadly, this is the only thing that is guaranteed to not happen in this case. The outcome can be one of two scenarios: if NULL is defined to be 0 (int zero), then the compiler will happily cast the spell with ID 0, since int 0 is a perfect match for the second overload. If it is defined to be another integral type like 0L (long int zero) as it is in current gcc and Clang implementations, the compiler will complain about an ambiguous call – 0L can be equally well converted to a null pointer and int.

Let’s “fix” this by using an enum instead of an int and therefore removing the ambiguity. While we are at it, let’s make our intent clear by giving the parameter a name:

enum SpellID { /* ... */ };
void castSpell(Spell* theSpell);
void castSpell(SpellID spellID);

int main() {
  auto noSpell = NULL;
  castSpell(noSpell); //ERROR
}

Here, noSpell is no kind of pointer, it is an integral variable. The conversion from zero integers to pointers, on the other hand, can only happen from null constants. Therefore the compiler will yell at us because it does not know any implicit conversion that could convert the variable of type long into either a SpellID or a Spell*.

The issues with NULL

The two examples have the same cause: NULL is just a macro, which is a code smell at best. It is an integer, not a pointer, so the type is just wrong. And then there is the issue of not being able to define a variable that is a null pointer without explicitly stating the pointer type.

nullptr to the rescue

Since C++11, there is a nice little feature that solves all those issues at once. nullptr is a literal and a null pointer constant, so it is implicitly convertible to any pointer type like 0 and 0L etc. Let’s revisit our two examples above, this time with nullptr:

void castSpell(Spell* theSpell);
void castSpell(int spellID);

int main() {
  castSpell(nullptr); //casting without an actual spell
}

Here, the compiler will do the thing we expect it to do: since nullptr is not convertible to int, it will convert to a null Spell* and call the correct function.

void castSpell(Spell* theSpell);
void castSpell(SpellID spellID);

int main() {
  auto noSpell = nullptr;
  castSpell(noSpell);
}

nullptr has it’s own type, std::nullptr_t, which is also convertible to pointer types implicitly. So the variable noSpell has now the type nullptr_t and is convertible to Spell*, but not to SpellID, so again the first overload is called.

Use nullptr instead of NULL, 0 or any other null pointer constant, wherever you need a generic null pointer.

nullptr and smart pointers

Smart pointers are no actual pointers, they are classes. So all the implicit conversions above do not take place when you use shared_ptretc. Luckily, since nullptrhas it’s own type, the smart pointer classes can have overloaded constructors and assignment operators for that type, so the following is valid:

shared_ptr<Spell> spellPtr = nullptr;
unique_ptr<Champion> champinoPtr = nullPtr;

Note that, except for the conversion from auto_ptr to unique_ptr, this is the only possible implicit constructor for the smart pointer classes. That way you can pass nullptr to functions that expect a smart pointer without having to explicitly create an empty smart pointer object:

void consumeObject(unique_ptr<Object> object);

int main() {
  consumeObject(nullptr);
}

nullptr and other classes

It turns out, that besides pointers and smart pointers there are other types that benefit from the notion of a typesafe “nothing” or “empty” type. While often it is sufficient to provide a default constructor that does create an object of empty state, a constructor that accepts a nullptr_t as argument can facilitate the expression of that empty state, e.g. in generic programming contexts. An example for such a class is std::function where the nullptr_t constructor does the same as the default constructor.

Backward compatibility

Besides the implicit conversion from any null pointer constant to pointers, the standard defines that any integral null pointer constant (i.e. 0, NULL etc.) is implicitly convertible to nullptr, i.e. if you introduce the use of nullptr and nullptr_t to an old code base, you are unlikely to break the compilation.

This holds especially for the introduction of  smart pointers: consider if we want to introduce unique_ptr to the following C++03 version of our last example:

//C++03 version:
void consumeObject(Object* object) {
  //...
  delete object;
}

int main() {
  consumeObject(NULL);
}

While we would do well replacing both the owning raw pointer with a unique_ptr and the NULL with nullptr, we can do the more pressing thing first: removing the potential memory leak by introducing the smart pointer:

//introduced unique_ptr - TODO: NULL -> nullptr
void consumeObject(unique_ptr<Object> object) {
  //...
}

int main() {
  consumeObject(NULL);
}

This just works. NULL is an integral null pointer constant which gets implicitly converted to nullptr, so the right unique_ptr constructor gets called. Just don’t leave the TODO in your code base for too long 😉

Conclusion

nullptr is a useful little feature that makes your code safer and at least as expressive as NULL. There is no excuse to not use it right away.

Previous Post
Next Post

1 Comment


  1. Actually nullptr is not a “new little nice feature” but a patch fixing the old, going from early C, issue. In fact the null literal is quite common for the most of modern PLs.
    It is interpreted in the way how it should be: just a reference to nothing. No implicit conversions to integers or to whatever.

    For classes like autopointer it could be easyly introduced.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *