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 abiguous 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); //ERROR
}

`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_ptr`etc. Luckily, since `nullptr`has 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.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

1 Comment

  1. Julian Drachevsky

    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 *