A really nice book I was recommended recently: Effective Modern C++ by Scott Meyers.
Neither 0
nor NULL
has a pointer type.
Consider below code:
void f(int); // three overloads of f
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls
// f(int). Never calls f(void*)
nullptr
’s actual type is std::nullptr_t
f(nullptr); // calls f(void*) overload`
It can also improve code clarity, especially when auto variables are involved.
For example, suppose you encounter this in a code base:
auto result = findRecord( /* arguments */ );
if (result == 0) {}
If you don’t happen to know (or can’t easily find out) what findRecord
returns, it may not be clear whether result
is a pointer type or an integral type. After all, 0
(what result
is tested against) could go either way. If you see the following, on the other hand,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {}
there’s no ambiguity: result
must be a pointer type.
nullptr
shines especially brightly when templates enter the picture. Suppose you have some functions that should be called only when the appropriate mutex has been locked. Each function takes a different kind of pointer:
int f1(std::shared_ptr<Widget> spw); // call these only when
double f2(std::unique_ptr<Widget> upw); // the appropriate
bool f3(Widget* pw); // mutex is locked
Calling code that wants to pass null pointers could look like this:
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxGuard = std::lock_guard<std::mutex>;
{
MuxGuard g(f1m); // lock mutex for f1
auto result = f1(0); // pass 0 as null ptr to f1
} // unlock mutex
{
MuxGuard g(f2m); // lock mutex for f2
auto result = f2(NULL); // pass NULL as null ptr to f2
} // unlock mutex
{
MuxGuard g(f3m); // lock mutex for f3
auto result = f3(nullptr); // pass nullptr as null ptr to f3
} // unlock mutex
The failure to use nullptr
in the first two calls in this code is sad, but the code works, and that counts for something. However, the repeated pattern in the calling code—lock mutex, call function, unlock mutex—is more than sad. It’s disturbing. This kind of source code duplication is one of the things that templates are designed to avoid, so let’s templatize the pattern:
template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
{
MuxGuard g(mutex);
return func(ptr);
}
If the return type of this function auto … -> decltype(func(ptr)
has you scratching your head, you’ll see that in C++14, the return type could be reduced to a simple decltype(auto)
:
template<typename FuncType, typename MuxType, typename PtrType>
decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
MuxGuard g(mutex);
return func(ptr);
}
Given the lockAndCall template (either version), callers can write code like this:
auto result1 = lockAndCall(f1, f1m, 0); // error!
auto result2 = lockAndCall(f2, f2m, NULL); // error!
auto result3 = lockAndCall(f3, f3m, nullptr); // fine
The fact that template type deduction deduces the “wrong” types for 0
and NULL
(i.e., their true types, rather than their fallback meaning as a representation for a null pointer) is the most compelling reason to use nullptr
instead of 0
or NULL
when you want to refer to a null pointer. With nullptr
, templates pose no special challenge. Combined with the fact that nullptr
doesn’t suffer from the overload resolution surprises that 0
and NULL
are susceptible to, the case is ironclad. When you want to refer to a null pointer, use nullptr
, not 0
or NULL
.