Skip to main content

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:

  1. Type ambiguity: Since NULL is essentially an integer, it could cause ambiguity in function overloading
  2. Implicit conversions: NULL could be implicitly converted to numeric types, leading to unexpected behavior
  3. Unclear intentions: Using 0 as a null pointer value wasn't self-documenting

Let's see these issues in action:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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:

cpp
#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

  1. Always use nullptr instead of NULL or 0 for null pointers in modern C++ code.
  2. Consider making pointer parameters explicit in function declarations instead of using 0 as a default value.
  3. Remember that nullptr converts to false in boolean contexts, so you can write cleaner conditional checks:
    cpp
    if (ptr) {  // Same as if (ptr != nullptr)
    // ptr is not null
    }
  4. Be explicit when checking against nullptr in 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

  1. Convert a small program that uses NULL for null pointers to use nullptr instead.
  2. Write a function that accepts multiple pointer parameters and safely checks them for nullness.
  3. Create a function overload set that distinguishes between integer and pointer parameters, and demonstrate how nullptr resolves ambiguity.
  4. Implement a template function that behaves differently when passed a null pointer versus a valid pointer.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)