C++ 2011: nullptr

C++ used to lack a special value to indicate a null pointer. It used 0 for this purpose, something that was inherited from C. This led to some problems, which we’ll show in this article. The new standard introduces a new reserved word nullptr, to designate a constant rvalue that represents a null pointer.

NULL is ZERO

In C++ a null pointer is:

A null pointer constant is an integral constant expression (expr.const) rvalue of integer type that evaluates to zero.

This was inherited from C where

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

Since there is no keyword in the language for a null pointer, both C and C++ compiler provide an implementation-dependent macro called NULL. In VC++ and GCC this is defined as:

#ifdef __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif

Integer 0 implicitly converts to any pointer type. The use of 0 as a special value for null pointers can lead to some issues, the most important being with calling overloaded functions. Let’s look at these two overloaded functions:

void foo(int)   {cout << "int" << endl;}
void foo(char*) {cout << "pointer" << endl;}

The q uestion is, which of the two would be called for foo(0)? The answer is the first one. To be able to call the second one with a null pointer an explicit cast to char* should be made, i.e. foo((char*)0).
On the other hand, the following line of code is valid and calls that constructor of std::basic_string that takes a char*:

string str(false);

The nullptr Reserved Word

The new standard introduces a reserved word called nullptr to represent a null pointer. It designates a constant rvalue of type decltype(nullptr):

typedef decltype(nullptr) nullptr_t;

The nullptr_t type:

  • Is a typedef for decltype(nullptr)
  • Is a POD type convertible to a pointer type and pointer-to-member type
  • Objects of this type can be copied and thrown
  • It is possible to take the address of a nullptr_t object
  • reinterpret_cast to/from a nullptr_t object is possible
  • nullptr_t matches a T* and a T::* partial specialization

The nullptr:

  • Is a literal (just like 0 or false)
  • The address of nullptr cannot be taken
  • Can be used with sizeof, typeid and ternary conditional operator

It is important to note that the standard library macro NULL is not (re-)defined to nullptr; the result of that would be a lot of broken code.

It is possible to use 0 (NULL) and nullptr interchangeably (which is important for compatibility), like in the following examples:

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

if(p1 == 0) {}
if(p2 == 0) {}
if(p1 == nullptr) {}
if(p2 == nullptr) {}
if(p1 == p2) {}
if(p2) {}

It is not possible however to assign nullptr to an integer value, nor to compare nullptr to an integer.

int n1 = 0;             // ok
int n2 = nullptr;       // error

if(n1 == nullptr) {}    // error
if(n2 == nullptr) {}    // error
if(nullprt) {}          // error

Also, because nullptr is an rvalue constant, it’s not possible to assign it a value.

nullptr = 0;

For the overloading problem presented in the previous paragraph, use of nullptr would call the overload that takes a char*.

void foo(int)   {cout << "int" << endl;}
void foo(char*) {cout << "pointer" << endl;}

foo(0);       // calls foo(int)
foo(nullptr); // calls foo(char*)

nullptr can be used with the ternary conditional operator:

int* p1 = ...;
int* p2 = true ? p1 : nullptr;

However, the following cases are erroneous:

int* p2 = true ? 0 : nullptr;       // incompatible types
int n1 = true ? nullptr : nullptr;  // nullptr cannot be converted to int
int n2 = true ? 0 : nullptr;        // incompatible types

Given these two template functions:

template<typename T>
void fooptr(T* t);

template<typename T>
void foo(T t);

The following cases are possible:

fooptr((int*)0);        // type T deduced to int
fooptr((int*)nullptr);  // type T deduced to int

foo(0);                 // type T deduced to int
foo((int*)0);           // type T deduced to int*
foo(nullptr);           // type T deduced to nullptr_t
foo((int*)nullptr);     // type T deduced to int*

while these are errors:

fooptr(0);              // error, type T cannot be deduced
fooptr(nullptr);        // error, type T cannot be deduced

Conclusions

The new C++ standard defines a reserved word nullptr (representing a constant rvalue of type nullptr_t) that designates a null pointer. For compatibility reasons 0 can still be used as null pointer, and 0 and nullptr can be used interchangeably; also for compatibility reasons, macro NULL will not be redefined to nullptr.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read