NULL vs nullptr (Why was it replaced?)
Categories:
NULL vs. nullptr in C++: Understanding the Evolution of Null Pointers

Explore the historical context, technical differences, and best practices for using NULL
and nullptr
in C++. Understand why nullptr
was introduced and how it enhances type safety and reduces common programming errors.
In C and early C++, the macro NULL
was the standard way to represent a null pointer. However, its implementation as an integer literal often led to subtle bugs and type-safety issues, especially in overloaded function scenarios. With the advent of C++11, a new keyword, nullptr
, was introduced to address these shortcomings, providing a type-safe and distinct null pointer constant. This article delves into the reasons behind this change, the technical differences between NULL
and nullptr
, and why nullptr
is now the preferred choice in modern C++ development.
The Problem with NULL: An Integer in Disguise
Historically, NULL
is typically defined as either 0
or (void*)0
. While this worked for basic pointer assignments, it created ambiguity when functions were overloaded to accept both integer types and pointer types. The compiler would often struggle to determine which overload to call when NULL
was passed, leading to unexpected behavior or compilation errors. This ambiguity stemmed from NULL
not having a distinct pointer type itself; it was merely an integer literal or a void*
that implicitly converted to other pointer types.
#include <iostream>
void func(int i) {
std::cout << "func(int) called with: " << i << std::endl;
}
void func(char* p) {
std::cout << "func(char*) called with: " << static_cast<void*>(p) << std::endl;
}
int main() {
// This call is ambiguous with some compilers/settings, or resolves to func(int)
// because NULL is often defined as 0.
// func(NULL); // Potential compilation error or unexpected behavior
// To explicitly call the pointer version, a cast is needed with NULL
func(static_cast<char*>(NULL));
// With nullptr, the intent is clear and type-safe
func(nullptr); // Calls func(char*) as expected
return 0;
}
Demonstration of NULL
ambiguity versus nullptr
type safety in function overloading.
flowchart TD A[Program Start] B{Call func(NULL)?} C[NULL defined as 0] D[NULL defined as (void*)0] E[func(int) overload] F[func(char*) overload] G[Ambiguity/Error] H[Call func(nullptr)?] I[nullptr is std::nullptr_t] J[Resolves to func(char*)] A --> B B -- Yes --> C B -- Yes --> D C --> G D --> G G --> E G --> F A --> H H -- Yes --> I I --> J
Flowchart illustrating the ambiguity of NULL
versus the clear resolution with nullptr
.
Introducing nullptr: A Type-Safe Null Pointer Constant
nullptr
was introduced in C++11 as a keyword of type std::nullptr_t
. This type is implicitly convertible to any pointer type, but not to any integral type (except bool
). This crucial distinction eliminates the ambiguity that plagued NULL
. When nullptr
is used in an overloaded function call, the compiler can unambiguously select the pointer overload because nullptr
has a distinct pointer-like type, unlike the integer literal 0
or (void*)0
that NULL
often represents.
nullptr
over NULL
in modern C++ code. It provides stronger type safety, reduces potential bugs, and makes your code more readable and maintainable.Key Differences and Benefits of nullptr
The primary benefit of nullptr
is its type safety. It prevents accidental conversions to integral types and ensures that pointer-related operations are handled correctly by the compiler. This leads to fewer runtime errors and more robust code. Additionally, nullptr
is a keyword, not a macro, which means it respects C++'s scope rules and is not subject to macro redefinition issues. It also makes the intent of representing a null pointer explicit and clear, improving code readability.

Key differences between NULL
and nullptr
.
#include <iostream>
// Overloaded functions
void process(int i) {
std::cout << "Processing integer: " << i << std::endl;
}
void process(double* p) {
std::cout << "Processing double pointer: " << static_cast<void*>(p) << std::endl;
}
void process(char* p) {
std::cout << "Processing char pointer: " << static_cast<void*>(p) << std::endl;
}
int main() {
// With NULL:
// process(NULL); // ERROR: ambiguous call to overloaded function 'process'
// Must cast NULL to resolve ambiguity for pointer types
process(static_cast<double*>(NULL)); // Calls process(double*)
process(static_cast<char*>(NULL)); // Calls process(char*)
// With nullptr:
process(nullptr); // Calls process(double*) or process(char*) depending on best match, usually the first pointer type.
// If only one pointer overload exists, it's unambiguous.
// If multiple pointer overloads exist, it might still be ambiguous if no best match.
// However, it will *never* call an integer overload.
// Example of nullptr's type safety preventing integer conversion:
// int x = nullptr; // ERROR: cannot convert 'std::nullptr_t' to 'int'
bool b = nullptr; // OK: nullptr can convert to bool (false)
std::cout << "nullptr as bool: " << std::boolalpha << b << std::endl;
return 0;
}
Further examples demonstrating nullptr
's type safety and NULL
's pitfalls.