C++ Nullptr
Introduction
When working with pointers in C++, you'll often need to represent the concept of a "null pointer" - a pointer that doesn't point to any valid memory location. Before C++11, developers typically used NULL or 0 for this purpose, which led to various issues and confusion. C++11 introduced the nullptr keyword, a dedicated null pointer constant that provides better type safety and clearer code.
In this article, we'll explore what nullptr is, why it was introduced, and how to use it effectively in your C++ programs.
What is nullptr?
nullptr is a C++11 keyword that represents a null pointer value. It is of type std::nullptr_t, which can be implicitly converted to any pointer type, but not to integral types (except bool).
The Problem with NULL
Before nullptr was introduced, C++ used the NULL macro (inherited from C), which is typically defined as 0 or 0L. This led to several issues:
- Type ambiguity: Since
NULLis essentially an integer, it could cause ambiguity in function overloading - Implicit conversions:
NULLcould be implicitly converted to numeric types, leading to unexpected behavior - Unclear intentions: Using
0as a null pointer value wasn't self-documenting
Let's see these issues in action:
#include <iostream>
void func(int n) {
std::cout << "func(int) called with: " << n << std::endl;
}
void func(char* p) {
std::cout << "func(char*) called" << std::endl;
}
int main() {
// Which function will be called?
func(NULL); // Calls func(int) because NULL is typically defined as 0
return 0;
}
Output:
func(int) called with: 0
This might be surprising! We probably intended to call the pointer version of the function, but because NULL is typically defined as 0, the func(int) overload was called instead.
How to Use nullptr
Using nullptr is straightforward - you use it just like you would use NULL or 0 for pointer initialization or comparison:
#include <iostream>
int main() {
// Initialize pointers to nullptr
int* p1 = nullptr;
double* p2 = nullptr;
char* p3 = nullptr;
// Check if a pointer is null
if (p1 == nullptr) {
std::cout << "p1 is null" << std::endl;
}
// Simplified null check (implicit conversion to bool)
if (!p2) {
std::cout << "p2 is null" << std::endl;
}
return 0;
}
Output:
p1 is null
p2 is null
Benefits of nullptr
1. Type Safety
The most significant advantage of nullptr is improved type safety. Unlike NULL or 0, nullptr has its own type (std::nullptr_t) and won't implicitly convert to integral types (except bool).
Let's revisit our earlier example with nullptr:
#include <iostream>
void func(int n) {
std::cout << "func(int) called with: " << n << std::endl;
}
void func(char* p) {
std::cout << "func(char*) called" << std::endl;
}
int main() {
func(nullptr); // Now this calls func(char*) as expected
return 0;
}
Output:
func(char*) called
2. Self-Documenting Code
Using nullptr makes your code more self-explanatory. When you see nullptr, you immediately know we're dealing with a pointer value, not just any zero value.
3. Template Support
nullptr works better with templates and type deduction. Here's an example:
#include <iostream>
#include <type_traits>
template<typename T>
void checkType(T value) {
std::cout << "Is pointer type? "
<< std::is_pointer<T>::value << std::endl;
}
int main() {
checkType(0); // T is deduced as int
checkType(NULL); // T is typically deduced as int
checkType(nullptr); // T is deduced as std::nullptr_t
return 0;
}
Output:
Is pointer type? 0
Is pointer type? 0
Is pointer type? 0
Even though the last example doesn't show true (because nullptr_t itself isn't a pointer type), it behaves correctly in other template contexts where it will convert to the appropriate pointer type.
Practical Examples
Example 1: Safer Function Parameters
Using nullptr makes function signatures more clear:
#include <iostream>
#include <string>
// This function accepts an optional string pointer
void processName(const std::string* name = nullptr) {
if (name) {
std::cout << "Processing name: " << *name << std::endl;
} else {
std::cout << "No name provided" << std::endl;
}
}
int main() {
std::string name = "Alice";
processName(&name); // Pass a valid pointer
processName(); // Use the default nullptr
return 0;
}
Output:
Processing name: Alice
No name provided
Example 2: Building a Simple Smart Pointer
Here's a simplified example showing how nullptr can be used in creating a basic smart pointer:
#include <iostream>
template<typename T>
class SimpleUniquePtr {
private:
T* ptr;
public:
SimpleUniquePtr(T* p = nullptr) : ptr(p) {}
// Destructor
~SimpleUniquePtr() {
delete ptr;
}
// No copy
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;
// Move constructor
SimpleUniquePtr(SimpleUniquePtr&& other) : ptr(other.ptr) {
other.ptr = nullptr; // Reset the source pointer
}
// Dereference operators
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
// Check if the pointer is valid
explicit operator bool() const { return ptr != nullptr; }
// Reset the pointer
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
};
int main() {
// Create a smart pointer to an int
SimpleUniquePtr<int> smartInt(new int(42));
if (smartInt) {
std::cout << "Smart pointer points to: " << *smartInt << std::endl;
}
// Reset the pointer
smartInt.reset();
if (!smartInt) {
std::cout << "Smart pointer is now null" << std::endl;
}
return 0;
}
Output:
Smart pointer points to: 42
Smart pointer is now null
Example 3: Safe Pointer Comparison
The nullptr makes pointer comparisons more readable:
#include <iostream>
#include <memory>
void processData(std::shared_ptr<int> data) {
// With nullptr, the intention is clear
if (data != nullptr) {
std::cout << "Processing data: " << *data << std::endl;
} else {
std::cout << "No data to process" << std::endl;
}
// This is also valid and commonly used:
if (data) {
std::cout << "Data exists" << std::endl;
}
}
int main() {
auto data1 = std::make_shared<int>(100);
std::shared_ptr<int> data2 = nullptr;
processData(data1);
processData(data2);
return 0;
}
Output:
Processing data: 100
Data exists
No data to process
Best Practices
- Always use
nullptrinstead ofNULLor0for null pointers in modern C++ code. - Consider making pointer parameters explicit in function declarations instead of using
0as a default value. - Remember that
nullptrconverts tofalsein boolean contexts, so you can write cleaner conditional checks:if (ptr) { // Same as if (ptr != nullptr)
// ptr is not null
} - Be explicit when checking against
nullptrin educational code for clarity, even though the simplified form is common in practice.
Summary
The nullptr keyword is one of the many improvements introduced in C++11 that makes C++ code safer and more expressive. By using nullptr:
- You avoid type ambiguity problems in function overloads
- Your code becomes more self-documenting
- You get better type safety and clearer intentions
- Your code works better with templates and modern C++ features
Using nullptr consistently is a simple change that immediately improves your code quality. It's one of those small modern C++ features that has a big impact on code clarity and correctness.
Exercises
- Convert a small program that uses
NULLfor null pointers to usenullptrinstead. - Write a function that accepts multiple pointer parameters and safely checks them for nullness.
- Create a function overload set that distinguishes between integer and pointer parameters, and demonstrate how
nullptrresolves ambiguity. - Implement a template function that behaves differently when passed a null pointer versus a valid pointer.
Additional Resources
- C++ Reference: nullptr
- C++ Core Guidelines: ES.47: Use nullptr rather than 0 or NULL
- Effective Modern C++ by Scott Meyers (Item 8: Prefer nullptr to 0 and NULL)
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!