174

This piece of code conceptually does the same thing for the three pointers (safe pointer initialization):

int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;

And so, what are the advantages of assigning pointers nullptr over assigning them the values NULL or 0?

Visruth
  • 3,430
  • 35
  • 48
Mark Garcia
  • 17,424
  • 4
  • 58
  • 94
  • 40
    For one thing, an overloaded function taking `int` and `void *` won't choose the `int` version over the `void *` version when using `nullptr`. – chris Dec 11 '12 at 08:36
  • 2
    Well `f(nullptr)` is different from `f(NULL)`. But as far as the above code is concerned(assigning to a local variable), all the three pointers are exactly the same. The only advantage is code readability. – balki Dec 11 '12 at 18:00
  • 2
    I am in favor of making this an FAQ, @Prasoon. Thanks! – sbi Dec 12 '12 at 11:01
  • 1
    NB NULL is historically not guaranteed to be 0, but is as oc C99, in the much the same way a byte wasn't necessarily 8 bits long and true and false were architecture dependant values. This question focuses on `nullptr` butthat's the difference between 0 and `NULL` – awiebe Sep 04 '18 at 03:01

7 Answers7

193

In that code, there doesn't seem to be an advantage. But consider the following overloaded functions:

void f(char const *ptr);
void f(int v);

f(NULL);  //which function will be called?

Which function will be called? Of course, the intention here is to call f(char const *), but in reality f(int) will be called! That is a big problem1, isn't it?

So, the solution to such problems is to use nullptr:

f(nullptr); //first function is called

Of course, that is not the only advantage of nullptr. Here is another:

template<typename T, T *ptr>
struct something{};                     //primary template

template<>
struct something<nullptr_t, nullptr>{};  //partial specialization for nullptr

Since in template, the type of nullptr is deduced as nullptr_t, so you can write this:

template<typename T>
void f(T *ptr);   //function to handle non-nullptr argument

void f(nullptr_t); //an overload to handle nullptr argument!!!

1. In C++, NULL is defined as #define NULL 0, so it is basically int, that is why f(int) is called.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    As what Mehrdad had stated, these kinds of overloads are pretty rare. Are there other relevant advantages of `nullptr`? (No. I'm not demanding) – Mark Garcia Dec 11 '12 at 08:46
  • Well, I honestly don't know what `something` is for ;). Nice. – Mark Garcia Dec 11 '12 at 08:59
  • Wait. Why would I want to have `something sumThing`? – Mark Garcia Dec 11 '12 at 09:05
  • 2
    @MarkGarcia, This might be helpful: http://stackoverflow.com/questions/13665349/what-is-a-proper-use-case-of-stdnullptr-t-template-parameters – chris Dec 11 '12 at 09:09
  • @MarkGarcia: One might need `something` especially in template metaprogramming. – Nawaz Dec 11 '12 at 09:09
  • 9
    Your footnote seems backwards. `NULL` is required by the standard to have an integral type, and that's why it's typically defined as `0` or `0L`. Also I'm not sure I like that `nullptr_t` overload, since it catches *only* calls with `nullptr`, not with a null pointer of a different type, like `(void*)0`. But I can believe it has some uses, even if all it does is save you defining a single-valued place-holder type of your own to mean "none". – Steve Jessop Dec 11 '12 at 09:43
  • 1
    Another advantage (though admittedly minor) may be that `nullptr` has a well-defined numeric value whereas null pointer constants do not. A null pointer constant is converted to the null pointer of that type (whatever that is). It is required that two null pointers of the same type compare identically, and boolean conversion turns a null pointer into `false`. _Nothing else is required._ Therefore, it is possible for a compiler (silly, but possible) to use e.g. `0xabcdef1234` or some other number for the null pointer. On the other hand, `nullptr` is required to convert to numeric zero. – Damon Dec 11 '12 at 12:34
  • On a second thought, "silly" is not so silly at all. A compiler could use distinct null pointer values for every type (in an address range that will certainly segfault), so a crash handler or debugger could immediately tell the type of a dereferenced null pointer only by looking at the raw address. This would not only be "not silly", but actually quite cool. – Damon Dec 11 '12 at 12:42
  • @Damon: Are you implying that `NULL` could have a value other than `0`? – Nawaz Dec 11 '12 at 13:22
  • This is incorrect. The real motivation was the need for forwarding. This trivial case can be solved by a cast. – Puppy Dec 11 '12 at 13:53
  • 2
    @DeadMG: What is incorrect in my answer? that `f(nullptr)` will not call the intended function? There were more than one motivations. Many other useful things can be discovered by the programmers themselves in the coming years. So you cannot say that there is *only one true usage* of `nullptr`. – Nawaz Dec 11 '12 at 14:31
  • 1
    @DeadMG: Also, the downvoting is silly *even if* my answer doesn't talk about the _real_ motivation. It is like downvoting an answer which talks about C++ template metaprogramming, giving a silly reason that "template was NOT added to do metaprogramming" (yes, it was DISCOVORED). – Nawaz Dec 11 '12 at 14:48
  • @Nawaz: Yes, of course. The standard says that null literals are equivalent to a numeric value of zero (the literals, not the assigned pointers!), and they convert to the null pointer of the respective type. There are additional constraints (see above) but it does not say that the null pointer of a type must have any particular numeric value. In fact, by stating that nullpointers _of equal type_ compare equal, it is explicitly _not_ guaranteed that two null pointers of _different_ types compare equal. So, if `int* a=0; char* b=0; assert(a == b);` fails, that't totally allowable. – Damon Dec 11 '12 at 15:35
  • Ok, it's not allowable to compare the pointers like this... but you get the idea. If you cast them to integers so it compiles, the assert may fail. – Damon Dec 11 '12 at 15:37
  • 1
    @Damon: No, `NULL` cannot be any value other than `0`. It must be zero. Who said *"it's not allowable to compare the pointers like this"*? You can compare pointers. – Nawaz Dec 11 '12 at 15:50
  • @Nawaz: Comparing distinct pointer types the way I did above isn't allowed (not without cast, anyway), therefore that won't compile, but that's irrelevant for what I was saying (adding a cast to `intptr_t` wouldn't have fit into a comment anyway). About `NULL` and `nullptr`, note that they're different things. `NULL` is a macro for `0`, an integer literal which is implicitly converted to a null pointer (not `nullptr`) value. And this certainly **can** be something other than zero. It is not specified what value a null pointer of some type has, so if it's zero, that's just convenience or luck. – Damon Dec 11 '12 at 16:39
  • 1
    The footnote needs correction. In C++ `NULL` has an implementation defined value, with possible definitions which can either `0` or `0L`. – Alok Save Jan 01 '13 at 16:28
  • Is this really supposed to be `template struct something {};`, or rather `template ...` (without the `*`)? – Taral Mar 13 '14 at 21:30
  • 1
    @Taral: Both will work. `nullptr` can initialize both `nullptr_t` and `nullptr_t*`!!! Why I used `T*`, because I wanted to demonstrate the pointer semantic. See [this](http://ideone.com/XRjtXk) ... and [this](http://ideone.com/zC41dq). I appreciate your comment BTW. :-) – Nawaz Mar 14 '14 at 07:53
  • @AlokSave: You mean the given example definitions for `NULL` are `0` and `0L`. It just needs to be a null-pointer constant implicitly convertible to any pointer type. `nullptr` fits the bill. – Deduplicator Jul 22 '14 at 14:37
92

C++11 introduces nullptr, it is known as the Null pointer constant and It improves type safety and resolves ambiguous situations unlike the existing implementation dependent null pointer constant NULL. To be able to understand the advantages of nullptr. we first need to understand what is NULL and what are the problems associated with it.


What is NULL exactly?

Pre C++11 NULL was used to represent a pointer that has no value or pointer that does not point to anything valid. Contrary to the popular notion NULL is not a keyword in C++. It is an identifier defined in standard library headers. In short you cannot use NULL without including some standard library headers. Consider the Sample program:

int main()
{ 
    int *ptr = NULL;
    return 0;
}

Output:

prog.cpp: In function 'int main()':
prog.cpp:3:16: error: 'NULL' was not declared in this scope

The C++ standard defines NULL as an implementation defined macro defined in certain standard library header files. The origin of NULL is from C and C++ inherited it from C. The C standard defined NULL as 0 or (void *)0. But in C++ there is a subtle difference.

C++ could not accept this specification as it is. Unlike C, C++ is a strongly typed language (C does not require explicit cast from void* to any type, while C++ mandates a explicit cast). This makes the definition of NULL specified by C standard useless in many C++ expressions. For example:

std::string * str = NULL;         //Case 1
void (A::*ptrFunc) () = &A::doSomething;
if (ptrFunc == NULL) {}           //Case 2

If NULL was defined as (void *)0, neither of above expressions would work.

  • Case 1: Will not compile because a automatic cast is needed from void * to std::string.
  • Case 2: Will not compile because cast from void * to pointer to member function is needed.

So unlike C, C++ Standard mandated to define NULL as numeric literal 0 or 0L.


So what is the need for another null pointer constant when we have NULL already?

Though the C++ Standards committee came up with a NULL definition which will work for C++, this definition had its own fair share of problems. NULL worked well enough for almost all scenarios but not all. It gave surprising and erroneous results for certain rare scenarios. For example:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    doSomething(NULL);
    return 0;
}

Output:

In Int version

Clearly, the intention seems to be to call the version which takes char* as the argument, but as the output shows the function which takes an int version gets called. This is because NULL is a numeric literal.

Furthermore, since it is implementation-defined whether NULL is 0 or 0L, there can lot of confusion in function overload resolution.

Sample Program:

#include <cstddef>

void doSomething(int);
void doSomething(char *);

int main()
{
  doSomething(static_cast <char *>(0));    // Case 1
  doSomething(0);                          // Case 2
  doSomething(NULL)                        // Case 3
}

Analyzing the above snippet:

  • Case 1: calls doSomething(char *) as expected.
  • Case 2: calls doSomething(int) but maybe char* version was be desired because 0 IS also a null pointer.
  • Case 3: If NULL is defined as 0, calls doSomething(int) when perhaps doSomething(char *) was intended, perhaps resulting in logic error at runtime. If NULL is defined as 0L, the call is ambiguous and results in compilation error.

So, depending on implementation, the same code can give various outcomes, which is clearly undesired. Naturally, the C++ standards committee wanted to correct this and that is the prime motivation for nullptr.


So what is nullptr and how does it avoid the problems of NULL?

C++11 introduces a new keyword nullptr to serve as null pointer constant. Unlike NULL, its behavior is not implementation-defined. It is not a macro but it has its own type. nullptr has the type std::nullptr_t. C++11 appropriately defines properties for the nullptr to avoid the disadvantages of NULL. To summarize its properties:

Property 1: it has its own type std::nullptr_t, and
Property 2: it is implicitly convertible and comparable to any pointer type or pointer-to-member type, but
Property 3: it is not implicitly convertible or comparable to integral types, except for bool.

Consider the following example:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    char *pc = nullptr;      // Case 1
    int i = nullptr;         // Case 2
    bool flag = nullptr;     // Case 3

    doSomething(nullptr);    // Case 4
    return 0;
}

In the above program,

  • Case 1: OK - Property 2
  • Case 2: Not Ok - Property 3
  • Case 3: OK - Property 3
  • Case 4: No confusion - Calls char * version, Property 2 & 3

Thus introduction of nullptr avoids all the problems of good old NULL.

How and where should you use nullptr?

The rule of thumb for C++11 is simply start using nullptr whenever you would have otherwise used NULL in the past.


Standard References:

C++11 Standard: C.3.2.4 Macro NULL
C++11 Standard: 18.2 Types
C++11 Standard: 4.10 Pointer conversions
C99 Standard: 6.3.2.3 Pointers

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • I'm already practicing your last advice since I've known `nullptr`, though I did not know what difference it really does to my code. Thanks for the great answer and especially for the effort. Brought me a great deal light on the topic. – Mark Garcia Jan 04 '13 at 07:15
  • "in certain standard library header files." -> why not just write "cstddef" from the beginning? – mxmlnkn Apr 14 '16 at 09:51
  • Why should we allow nullptr to be convertible to bool type? Could you please elaborate more? – Robert Wang Aug 26 '16 at 07:00
  • *...was used to represent a pointer that has no value...* Variables **always** has a value. It may be noise, or `0xccccc....`, but, a value-less variable is an inherent contradiction. – 3Dave Nov 11 '16 at 01:06
  • "Case 3: OK - Property 3" (line `bool flag = nullptr;`). Nope, not OK, I get the following error at compile time with g++ 6: `error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]` – Georg Dec 08 '18 at 13:35
24

The real motivation here is perfect forwarding.

Consider:

void f(int* p);
template<typename T> void forward(T&& t) {
    f(std::forward<T>(t));
}
int main() {
    forward(0); // FAIL
}

Simply put, 0 is a special value, but values cannot propagate through the system- only types can. Forwarding functions are essential, and 0 can't deal with them. Thus, it was absolutely necessary to introduce nullptr, where the type is what is special, and the type can indeed propagate. In fact, the MSVC team had to introduce nullptr ahead of schedule after they implemented rvalue references and then discovered this pitfall for themselves.

There are a few other corner cases where nullptr can make life easier- but it's not a core case, as a cast can solve these problems. Consider

void f(int);
void f(int*);
int main() { f(0); f(nullptr); }

Calls two separate overloads. In addition, consider

void f(int*);
void f(long*);
int main() { f(0); }

This is ambiguous. But, with nullptr, you can provide

void f(std::nullptr_t)
int main() { f(nullptr); }
Puppy
  • 144,682
  • 38
  • 256
  • 465
5

Basics of nullptr

std::nullptr_t is the type of the null pointer literal, nullptr. It is a prvalue/rvalue of type std::nullptr_t. There exist implicit conversions from nullptr to null pointer value of any pointer type.

The literal 0 is an int, not a pointer. If C++ finds itself looking at 0 in a context where only a pointer can be used, it’ll grudgingly interpret 0 as a null pointer, but that’s a fallback position. C++’s primary policy is that 0 is an int, not a pointer.

Advantage 1 - Remove ambiguity when overloading on pointer and integral types

In C++98, the primary implication of this was that overloading on pointer and integral types could lead to surprises. Passing 0 or NULL to such overloads never called a pointer overload:

   void fun(int); // two overloads of fun
    void fun(void*);
    fun(0); // calls f(int), not fun(void*)
    fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)

The interesting thing about that call is the contradiction between the apparent meaning of the source code (“I am calling fun with NULL-the null pointer”) and its actual meaning (“I am calling fun with some kind of integer— not the null pointer”).

nullptr’s advantage is that it doesn’t have an integral type. Calling the overloaded function fun with nullptr calls the void* overload (i.e., the pointer overload), because nullptr can’t be viewed as anything integral:

fun(nullptr); // calls fun(void*) overload 

Using nullptr instead of 0 or NULL thus avoids overload resolution surprises.

Another advantage of nullptr over NULL(0) when using auto for return type

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.

Advantage 3

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

void lockAndCallF1()
{
        MuxtexGuard g(f1m); // lock mutex for f1
        auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
        cout<< result<<endl;
}

void lockAndCallF2()
{
        MuxtexGuard g(f2m); // lock mutex for f2
        auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
        cout<< result<<endl;
}
void lockAndCallF3()
{
        MuxtexGuard g(f3m); // lock mutex for f2
        auto result = f3(nullptr);// pass nullptr as null ptr to f3 
        cout<< result<<endl;
} // unlock mutex
int main()
{
        lockAndCallF1();
        lockAndCallF2();
        lockAndCallF3();
        return 0;
}

Above program compile and executed successfully but lockAndCallF1, lockAndCallF2 & lockAndCallF3 have redundant code. It is pity to write code like this if we can write template for all these lockAndCallF1, lockAndCallF2 & lockAndCallF3. So it can be generalized with template. I have written template function lockAndCall instead of multiple definition lockAndCallF1, lockAndCallF2 & lockAndCallF3 for redundant code.

Code is re-factored as below:

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
        MuxtexGuard g(mutex);
        return func(ptr);
}
int main()
{
        auto result1 = lockAndCall(f1, f1m, 0); //compilation failed 
        //do something
        auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
        //do something
        auto result3 = lockAndCall(f3, f3m, nullptr);
        //do something
        return 0;
}

Detail analysis why compilation failed for lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) not for lockAndCall(f3, f3m, nullptr)

Why compilation of lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) failed?

The problem is that when 0 is passed to lockAndCall, template type deduction kicks in to figure out its type. The type of 0 is int, so that’s the type of the parameter ptr inside the instantiation of this call to lockAndCall. Unfortunately, this means that in the call to func inside lockAndCall, an int is being passed, and that’s not compatible with the std::shared_ptr<int> parameter that f1 expects. The 0 passed in the call to lockAndCall was intended to represent a null pointer, but what actually got passed was int. Trying to pass this int to f1 as a std::shared_ptr<int> is a type error. The call to lockAndCall with 0 fails because inside the template, an int is being passed to a function that requires a std::shared_ptr<int>.

The analysis for the call involving NULL is essentially the same. When NULL is passed to lockAndCall, an integral type is deduced for the parameter ptr, and a type error occurs when ptr—an int or int-like type—is passed to f2, which expects to get a std::unique_ptr<int>.

In contrast, the call involving nullptr has no trouble. When nullptr is passed to lockAndCall, the type for ptr is deduced to be std::nullptr_t. When ptr is passed to f3, there’s an implicit conversion from std::nullptr_t to int*, because std::nullptr_t implicitly converts to all pointer types.

It is recommended, Whenever you want to refer to a null pointer, use nullptr, not 0 or NULL.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Ajay yadav
  • 4,141
  • 4
  • 31
  • 40
4

There is no direct advantage of having nullptr in the way you have shown the examples.
But consider a situation where you have 2 functions with same name; 1 takes int and another an int*

void foo(int);
void foo(int*);

If you want to call foo(int*) by passing a NULL, then the way is:

foo((int*)0); // note: foo(NULL) means foo(0)

nullptr makes it more easy and intuitive:

foo(nullptr);

Additional link from Bjarne's webpage.
Irrelevant but on C++11 side note:

auto p = 0; // makes auto as int
auto p = nullptr; // makes auto as decltype(nullptr)
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 3
    For reference, `decltype(nullptr)` is `std::nullptr_t`. – chris Dec 11 '12 at 08:47
  • @chris Is `std::nullptr_t` a typedef, #define or something? – Mark Garcia Dec 11 '12 at 08:48
  • 2
    @MarkGarcia, It's a full-blown type as far as I know. – chris Dec 11 '12 at 08:49
  • @chris And so, is it a (real) pointer type? And if it is, what type does it point to? (I'm really curious. Seriously.) – Mark Garcia Dec 11 '12 at 08:50
  • 5
    @MarkGarcia, It's an interesting question. cppreference has: `typedef decltype(nullptr) nullptr_t;`. I guess I can look in the standard. Ah, found it: *Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value.* – chris Dec 11 '12 at 08:50
  • @chris Oh. They really like to always take things differently then. Still, thank you very much for the effort. – Mark Garcia Dec 11 '12 at 08:54
  • It's a really good thing you pointed out the deduction of `nullptr`. – Mark Garcia Dec 11 '12 at 09:06
  • This is quite incorrect. Perfect forwarding was the true motivation. – Puppy Dec 11 '12 at 13:53
  • 2
    @DeadMG: There were more than one motivations. Many other useful things can be discovered by the programmers themselves in the coming years. So you cannot say that there is *only one true usage* of `nullptr`. – Nawaz Dec 11 '12 at 14:34
  • I never said there was one true usage. I said there was one motivation, namely, that 0 cannot forward for shit and `nullptr` can. – Puppy Dec 11 '12 at 16:19
  • 2
    @DeadMG: But you said this answer is *"quite incorrect"* simply because it doesn't talk about *"the true motivation"* you talked about in your answer. Not only that this answer (and mine as well) received a downvote from you. – Nawaz Dec 11 '12 at 16:54
  • Well, since forwarding is the primary motivation, then any answer not discussing it is quite incorrect. – Puppy Dec 11 '12 at 22:03
  • 1
    @DeadMG: That is superlatively dumb. An answer is wrong when it gives you wrong information. But this answer (as well as mine) doesn't give any wrong information. – Nawaz Dec 12 '12 at 03:31
  • It suggests that the motivation for nullptr was something other than perfect forwarding, which it was not. – Puppy Dec 12 '12 at 12:41
  • 1
    @DeadMG: The motivation was obviously NOT one; there were many. (Wait, let me guess : now you're going to say "primary" motivation, right?) – Nawaz Dec 13 '12 at 16:37
4

Just as others have already said, its primary advantage lies in overloads. And while explicit int vs. pointer overloads can be rare, consider standard library functions like std::fill (which has bitten me more than once in C++03):

MyClass *arr[4];
std::fill_n(arr, 4, NULL);

Doesn't compile: Cannot convert int to MyClass*.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

IMO more important than those overload issues: in deeply nested template constructs, it's hard not to lose track of the types, and giving explicit signatures is quite an endeavour. So for everything that you use, the more precisely focused to the intended purpose, the better, it will reduce the need for explicit signatures and allows the compiler to produce more insightful error messages when something goes wrong.

RandomDSdevel
  • 431
  • 6
  • 17
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319