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.
Similar to many other C++ code bases, Sauce used the following code to define
#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 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.
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
// 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
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
// 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
'int'. This is much better. We know that there is a type mismatch in the comparison, and we can repair the issue.
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