The Null Pointer Constant nullptr

Contents[Show]

The new null pointer nullptr cleans up in C++ with the ambiguity of the number 0 and the macro NULL.

The number 0

The issue with the literal 0 is that is can be the null pointer (void*)0 or the number 0. This is up to the context. I admit, we are used to this oddity. But only almost.

Therefore, the small program with the number 0 should be confusing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// null.cpp

#include <iostream>
#include <typeinfo>

int main(){

  std::cout << std::endl;

  int a= 0;
  int* b= 0;
  auto c= 0;
  std::cout << typeid(c).name() << std::endl;

  auto res= a+b+c;
  std::cout << "res: " << res << std::endl;
  std::cout << typeid(res).name() << std::endl;
  
  std::cout << std::endl;

}

 

The question is. What is the data type of the variable c in line 12 and the variable res in line 15?

null

The variable c is of type int and the variable res is of type pointer to int: int*. Pretty simple, right? The expression a+b+c in line 15 is a pointer arithmetic.

The macro NULL

The issue with the null pointer NULL is that it implicitly converts to int. Not so nice.

According to en.cppreference.com the macro NULL is an implemenentation-defined null pointer constant. A possible implementation: 

#define NULL 0
//since C++11
#define NULL nullptr

 

But that will not apply to my platform. Null seems to be of the type long int. I will refer to this point later. The usage of the macro NULL raises some questions.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// nullMacro.cpp

#include <iostream>
#include <typeinfo>

std::string overloadTest(int){
  return "int";
}

std::string overloadTest(long int){
  return "long int";
}


int main(){

  std::cout << std::endl;
  
  int a= NULL;
  int* b= NULL;
  auto c= NULL;
  // std::cout << typeid(c).name() << std::endl;
  // std::cout << typeid(NULL).name() << std::endl;
  
  
  std::cout << "overloadTest(NULL)= " << overloadTest(NULL) << std::endl;
  
  std::cout << std::endl;

}

 

The compiler complains the implicit conversion to int in line 19. That's ok. But the warning in line 21 is confusing. The compiler automatically deduces the type of the variable c to long int. At the same time it complains that the expression NULL must be converted. My observation is in accordance with the call overloadTest(NULL) in line 26. The compiler uses the version for the type long int (line 10). If the implementation uses NULL of type int, the compiler will choose overloadTest for the parameter type int (line 6). That is fine according to the C++ standard.

nullMacro

Now I want to know the current type of the null pointer constant NULL. Therefore, I comment out the lines 22 and 23 of the program.

nullMacroType

NULL seems for the compiler on one hand of type long int and on the other hand a constant pointer. This behaviour shows the compilation of the program nullMacro.cpp.

I leaned my lesson. Don't use the macro NULL.

But we have our rescue with the new null pointer constant nullptr.

The null pointer constant nullptr

The new null pointer nullptr cleans up in C++ with the ambiguity of the number 0 and the macro NULL. nullptr is and remains of type std::nullptr_t.

You can assign arbitrary pointers to a nullptr. The pointer becomes a null pointer and points to no data. You can not dereference a nullptr. Pointer of this type can on one hand compared with all pointers and can on the other hand converted to all pointers. This holds also true for pointers to class members. But you can not compare and convert a nullptr to an integral type. There is one exception of this rule. You can implicitly compare and convert a bool value with a nullptr. Therefore, you can use a nullptr in a logical expression.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// nullptr.cpp

#include <iostream>
#include <string>

std::string overloadTest(char*){
  return "char*";
}

std::string overloadTest(long int){
  return "long int";
}

int main(){

  std::cout << std::endl;

  long int* pi = nullptr;       
  // long int i= nullptr;       // ERROR
  auto nullp= nullptr;          // type std::nullptr_t
  
  bool b = nullptr;           
  std::cout << std::boolalpha << "b: "  << b << std::endl;
  auto val= 5;
  if ( nullptr < &val ){ std::cout << "nullptr < &val" << std::endl; }  

  // calls char*
  std::cout << "overloadTest(nullptr)= " <<  overloadTest(nullptr)<< std::endl;

  std::cout << std::endl;

}

  

The nullptr can be used to initialize a pointer of type long int (line 18). But it can not be used to initialize a variable of type long int (line 18). The automatic type deduction in line 20 is quite interesting. nullp becomes a value of type std::nullptr_t. The null pointer constant behaves like a boolean value that what initialized with false. You can observe that in the lines 22 - 25. If the nullptr has to decide between a long int and a pointer, it will decide for a pointer (line 28).

Here is the output of the program.

 

nullptr 

The simple rule is: Use nullptr instead of 0 or NULL. Still, not convinced? Here is my last and strongest point.

Generic code

The literal 0 and NULL show in generic code their true nature. Thanks to template argument deduction both literals are integral types in the function template. There is no hint that both literals were null pointer constants.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// generic.cpp

#include <cstddef>
#include <iostream>
 
template<class P >
void functionTemplate(P p){
  int* a= p;
}
 
int main(){
  int* a= 0;           
  int* b= NULL;              
  int* c= nullptr;
 
  functionTemplate(0);   
  functionTemplate(NULL);
  functionTemplate(nullptr);  
}

 

You can use 0 and NULL to initialize the int pointer in line 12 and 13. But if you use the values 0 and NULL as arguments of the function template, the compiler will loudly complain. The compiler deduces 0 in the function template to type int; it deduces NULL to the type long int. But these observations will not hold true for the nullptr. nullptr is in line 12 of type std::nullptr_t and nullptr is in line 8 of type std::nullptr_t.

 generic

What' next?

I presented in my last post a lot of features in C++ to make your code safer. Which one? Have a look at high safety requirements on the overview page. The key ideas of all this features is it to use the smartness of compiler. Therefore, we follow one of the key principles of C++: Compile time errors are better than run time errors.

With the next posts I switch the focus. My focus will change from the C++ features that are import for safety critical features to the features that are important for performance reasons. I will have in the next post a deeper look into inline. Thanks to the keyword inline, the compiler can replace the function call with its function invocation. Therefore, the expensive call of the function becomes superfluous.

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Tags: nullptr

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 424

All 252655

Currently are 231 guests and no members online