Nullptr

This last week, I spent a couple days converting all of the uses of NULL in Sauce to nullptr. As such, I wanted to take this opportunity to jot down some notes on what the differences are and why I believe that it is worth your time to convert your own code to use nullptr if you haven’t already.

NULL

Similar to many other C++ code bases, Sauce used the following code to define NULL:

#if !defined(NULL)
   #define NULL	0
#endif

While it is true that many commercial software packages and games have shipped with this definition of NULL, it is still problematic.

The reason why is that NULL simply an integer, not an actual null pointer. The danger stems from the fact that other constructs can be implicitly converted to and from integers. This means that using NULL can hide bugs.

In fact, I’ll be the first to admit that during the transition to nullptr, I found a few of these types of conversion errors in Sauce. Sure, the code still compiled and ran — but it was a bit disheartening to find them nonetheless.

Nullptr

Unlike NULL, nullptr is a keyword (available starting in C++11). It is defined to be implicitly converted to any pointer type, but cannot be implicitly converted to an integer. This allows the compiler to recognize the sort of type mismatches we hope that it would.

Let’s walk through an example to see the difference.

An Example

The problem we will explore in this example is the fact that booleans can be compared with NULL without a compiler warning. This is because the C++ standard declares that there is an implicit conversion from bool to int.

// signature:
Result* DoSomething();

// client code:
if (DoSomething() == NULL)
   printf("NULL\n");
else
   printf("NOT NULL\n");

Although this example is a bit contrived, the danger it exemplifies is real.

What happens if we change the return type of DoSomething() from Result* to bool? A change like this is certainly not unheard of — instead of using a full object, maybe we feel like we can reduce the result to a simple boolean.

// signature:
bool DoSomething();

// client code:
if (DoSomething() == NULL)
   printf("NULL\n");
else
   printf("NOT NULL\n");

The code still compiles — no additional warnings (even on Warning level 4!). That seems wrong … checking if a boolean is equal to null pointer is nonsensical and the compiler should bark when it comes across code like this, right?

Unfortunately, the compiler can’t detect the issue because we are using a #define as a stand-in for a null pointer. Remember, it’s not really a null pointer, it’s just the same value that a null pointer evaluates to: 0. Therefore, we shouldn’t be surprised when corner cases like this result in unexpected behavior.

So what happens if we replace NULL with the nullptr keyword?

// signature:
bool DoSomething();

// client code:
if (DoSomething() == nullptr)
   printf("NULL\n");
else
   printf("NOT NULL\n");

Now the compiler will generate an error stating that there is no conversion from 'nullptr' to 'int'. This is much better. We know that there is a type mismatch in the comparison, and we can repair the issue.

Final Thoughts

Simply put, the nullptr keyword is a true null pointer, while NULL is not.

When I was first deciding whether I was going to undertake the conversion task, I felt a bit overwhelmed at the number of changes that I would have to make (at the time there were over 5000 instances of NULL in Sauce). However, as I mentioned earlier, had I not made the transition to nullptr, those silent implicit conversion bugs would surely still be there. As such, I feel that Sauce is far better off with nullptr.