What is nullptr and why should it replace NULL in modern C++?
The article explains that NULL is a macro equal to integer 0, which can cause type‑mixing errors in overload resolution and template deduction, while the C++11 keyword nullptr has its own std::nullptr_t type, providing strict pointer semantics, eliminating overload ambiguities, improving safety, readability, and integration with modern C++ features.
Understanding the traditional NULL macro
In C, NULL is typically defined as ((void*)0), allowing implicit conversion to any pointer type. In C++ it is often defined as the integer literal 0, which means it lacks a dedicated pointer type.
#include <stdio.h>
#include <stdlib.h>
int main(){
int *ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed
");
return 1;
}
free(ptr);
ptr = NULL;
return 0;
}When malloc fails, it returns NULL, which can be compared with a pointer because it is a void* constant in C. In C++ the integer definition leads to subtle bugs.
Problems caused by NULL in C++
Function overload ambiguity
Because NULL is an integer, overload resolution may prefer an int overload over a pointer overload.
#include <iostream>
void func(int num){
std::cout << "Called int version: " << num << std::endl;
}
void func(char* ptr){
if (ptr) std::cout << "Called pointer version: " << *ptr << std::endl;
else std::cout << "Called pointer version with null" << std::endl;
}
int main(){
func(NULL);
return 0;
}The call func(NULL) selects the int overload, producing the output “Called int version: 0”, which is often unexpected.
Template deduction issues
When NULL is used as a template argument, the compiler deduces int instead of a pointer type.
#include <iostream>
template<typename T>
void printType(T t){
if constexpr (std::is_same_v<T, int>)
std::cout << "Type is int" << std::endl;
else if constexpr (std::is_same_v<T, char*>)
std::cout << "Type is char*" << std::endl;
else if constexpr (std::is_same_v<T, decltype(nullptr)>)
std::cout << "Type is nullptr" << std::endl;
else
std::cout << "Other type" << std::endl;
}
int main(){
printType(NULL);
return 0;
}The call prints “Type is int”, demonstrating the type‑mixing problem.
Introducing nullptr
nullptris a keyword added in C++11. It has its own type std::nullptr_t, which can be implicitly converted to any pointer type but not to integral types.
int* p1 = nullptr;
double* p2 = nullptr;
char* p3 = nullptr;Attempting to assign nullptr to an integer causes a compilation error, ensuring type safety.
int num = nullptr; // error: cannot convert nullptr to intIn a boolean context, nullptr evaluates to false, matching the behavior of ordinary pointers.
if (nullptr) {
// never executed
} else {
std::cout << "nullptr evaluates to false" << std::endl;
}Why nullptr was added
Type safety : It cannot be confused with integers, preventing accidental assignments and overload mismatches.
Eliminating overload ambiguity : Calls like func(nullptr) unambiguously select the pointer overload.
Improved readability and maintainability : The intent of a null pointer is explicit.
Practical usage scenarios
Pointer initialization
int* ptr1 = nullptr;
double* ptr2 = nullptr;
char* ptr3 = nullptr;
if (ptr1 == nullptr) std::cout << "ptr1 is null" << std::endl;Condition checks
#include <iostream>
void processData(int* dataPtr){
if (dataPtr == nullptr) {
std::cout << "Data pointer is null, cannot process" << std::endl;
return;
}
std::cout << "Processing data: " << *dataPtr << std::endl;
}
int main(){
int* validPtr = new int(10);
int* nullPtr = nullptr;
processData(validPtr);
processData(nullPtr);
delete validPtr;
return 0;
}Function overload resolution
#include <iostream>
void handle(int num){ std::cout << "Handling int: " << num << std::endl; }
void handle(int* ptr){
if (ptr) std::cout << "Handling pointer value: " << *ptr << std::endl;
else std::cout << "Handling null pointer" << std::endl;
}
int main(){
handle(10);
handle(nullptr);
int value = 20;
handle(&value);
return 0;
}Calling handle(nullptr) selects the pointer overload, avoiding the ambiguity seen with NULL.
Template programming
#include <iostream>
#include <type_traits>
template<typename T>
void checkPointer(T* ptr){
if (ptr == nullptr) std::cout << "Pointer is null" << std::endl;
else std::cout << "Pointer points to valid data" << std::endl;
}
int main(){
int* p = nullptr;
checkPointer(p);
double* d = new double(3.14);
checkPointer(d);
delete d;
return 0;
}Because nullptr has a unique type, template deduction works correctly for any pointer type.
Smart pointers
#include <memory>
#include <iostream>
int main(){
std::shared_ptr<int> sp = nullptr;
std::unique_ptr<double> up = nullptr;
if (sp == nullptr) std::cout << "shared_ptr is null" << std::endl;
if (up == nullptr) std::cout << "unique_ptr is null" << std::endl;
sp = std::make_shared<int>(42);
up = std::make_unique<double>(1.618);
if (sp != nullptr) std::cout << "sp points to: " << *sp << std::endl;
if (up != nullptr) std::cout << "up points to: " << *up << std::endl;
return 0;
}Using nullptr with shared_ptr and unique_ptr makes the intent of an empty smart pointer explicit and integrates cleanly with other C++11 features.
Returning pointers from functions
#include <iostream>
class Node{ public: int data; Node* next; Node(int v): data(v), next(nullptr){} };
Node* findNode(Node* head, int target){
while (head != nullptr){
if (head->data == target) return head;
head = head->next;
}
return nullptr;
}
int main(){
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);
Node* r1 = findNode(head, 2);
if (r1) std::cout << "Found: " << r1->data << std::endl;
Node* r2 = findNode(head, 4);
if (!r2) std::cout << "Not found" << std::endl;
// cleanup omitted for brevity
return 0;
}Returning nullptr clearly signals that the search failed.
Conclusion
Because nullptr provides a dedicated null‑pointer type, it eliminates the type‑mixing pitfalls of NULL, resolves overload ambiguities, enhances code readability, and works seamlessly with modern C++ constructs such as smart pointers, auto, and lambda expressions. Therefore, using nullptr is the recommended practice in all new C++ code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
