which is which nullptr or 0 or NULL

which is which nullptr or 0 or NULL

·

3 min read

to be able to decide which is preferable to use in practice, let's take about the type of each one of these. 0 is always an int in all programming languages. and for NULL the type is a little miss leading, 0L which is 0 and long, but anyway it is also an int. it is that neither 0 nor NULL has a pointer type.

Overloading error

the problem started to appear when we use overloaded functions.

void f(int);
void f(bool);
void f(*void);
f(0);   // call f(int) not f(*void);
f(NULL);        // might not work but technically calls f(int) never calls f(*void);

so how to call the f(*void); with a param with ptr type?. Oh, damn, I forgot to mention the type of nullptr. well, the nullptr type is std::nullptr_t which is a ptr to all types of ptr. and in a very nice circular definition, std::nullptr_t is a type of nullptr. then calling the function with nullptr will call the function with the ptr parameter.

f(nullptr); // calls f(*void);

thus using nullptr has avoided the overload and resolved it in a really simple way.

Clean Code

how nullptr helps to clean the code? A wise man said "let's write some code"

// let's see what the wise man said
auto res=findPrim(//argument);
if(res==0){
//do something
}

At the first glance, you wouldn't be sure of the return type of the func, maybe it is a pointer or int. After all, 0 could go either way. but what is your first glance when you see the following code?

auto res=findPrim(//Argu//);
if(res==nullptr){} //for sure result is a pointer type.

Template with nullptr

nullptr in case of the template case is critical and must be considered. Suppose you have some functions that should be called only when the appropriate mutex has been locked. Each function takes a different kind of ptr.

int f1(std::shared_ptr<widget> spw);
double f2(std::unique_ptr<widget> upw);
bool f3(widget*pw); 
std::mutex f1m,f2m,f3m; // mutex for f1,f2,f3
using MuxGuard= std::lock_guard<std::mutex>;
{
MuxGuard g(f1m);  // lock mutex of f1
auto res=f1(0); // now reales the lock as we pass 0 and treat it like nullptr
}
MuxGuard g(f2m);  // lock mutex for f2
auto res = f2(NULL);  // pass NULL as null ptr to f2 unlock mutex

MuxGuard g(f3m);            // lock mutex for f3
auto res = f3(nullptr);

Surprisingly, the above code works pretty fine, that's what we didn't expect. what if templatize the pattern?


 template<typename FuncType,typename MuxType,typename PtrType>
decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) {//c++14

MuxGuard g(mutex);
return func(ptr);
}
auto res1=lockAndCall(f1,f1m,0); // error!
auto res2=lockAndCall(f2,f2m,NULL); // error!
auto res3=lockAndCall(f3,f3m,nullptr); // fine

why the above code didn't work when we used a templatized pattern, the answer resides in the template type deduction. when the 0 is passed to the template the type of 0 is always int, and as int is passed to the func as it is not compatible with shared_ptr<widget> is a type error. the same for NULL as an int-like type is passed to f2 which is a type error as it is not compatible with unique_ptr<widget>. in contrast, to all the above errors nullptr stands alone in a very white position. when the nullptr is passed the ptr is deduced to std::nullptr_t when ptr is passed to f3, there's an implicit conversion from std::nullptr_t to widget* because of the std::nullptr_t converts to all pointer types.

when you want to refer to a null pointer use nullptr not 0 or NULL.

Things to Remember

  • Prefer nullptr to 0 and NULL.

  • Avoid overloading on integral and pointer types.