Skip to content

Nullptr

C++11 introduced nullptr to replace the unsafe NULL macro and 0 for null pointer values. This chapter covers why nullptr matters and how to use it correctly.

In C++, NULL is typically defined as either 0 or ((void*)0), both of which cause problems.

#include <iostream>
void process(int value) {
std::cout << "process(int): " << value << "\n";
}
void process(const char* value) {
std::cout << "process(const char*): " << value << "\n";
}
void process(std::nullptr_t) {
std::cout << "process(nullptr_t)\n";
}
int main() {
process(NULL); // Which is called? Could be int or char*
process(0); // Most compilers choose int
process(nullptr); // Clearly calls nullptr_t version
return 0;
}
#include <iostream>
#include <type_traits>
void oldStyle(int* ptr) {
std::cout << "Pointer: " << ptr << "\n";
}
void oldStyle(int value) {
std::cout << "Integer: " << value << "\n";
}
int main() {
// These all compile but have issues
oldStyle(NULL); // Compiles to int 0
oldStyle(0); // Is it a pointer or int?
oldStyle(nullptr); // Unambiguously a pointer
// Type checking
std::cout << "Type of NULL: " << typeid(NULL).name() << "\n";
std::cout << "Type of nullptr: " << typeid(nullptr).name() << "\n";
return 0;
}

nullptr is a keyword representing a null pointer constant of type std::nullptr_t.

#include <iostream>
#include <memory>
int main() {
// All nullptr uses
int* intPtr = nullptr;
char* charPtr = nullptr;
void* voidPtr = nullptr;
int** ptrToPtr = nullptr;
// Smart pointers
std::unique_ptr<int> uptr = nullptr;
std::shared_ptr<int> sptr = nullptr;
// Function pointers
void (*funcPtr)() = nullptr;
// Member function pointers
int (MyClass::*memberPtr)() = nullptr;
std::cout << "All initialized to nullptr\n";
return 0;
}
#include <iostream>
#include <type_traits>
void process(std::nullptr_t nullp) {
std::cout << "Received nullptr\n";
}
int main() {
// nullptr has its own type
static_assert(
std::is_same_v<decltype(nullptr), std::nullptr_t>,
"nullptr is std::nullptr_t"
);
// Can pass nullptr to functions expecting nullptr_t
process(nullptr);
return 0;
}
#include <iostream>
#include <memory>
template<typename T>
void safeDelete(T* ptr) {
if (ptr != nullptr) {
delete ptr;
std::cout << "Deleted pointer\n";
} else {
std::cout << "Pointer was null, skipping delete\n";
}
}
// Template that works with smart pointers
template<typename Ptr>
void safeReset(Ptr& ptr) {
if (ptr) {
ptr.reset();
std::cout << "Reset smart pointer\n";
}
}
int main() {
int* raw = nullptr;
safeDelete(raw); // Handles null correctly
auto sp = std::make_shared<int>(42);
safeReset(sp);
return 0;
}
#include <iostream>
#include <memory>
#include <optional>
template<typename T>
std::optional<T> findInContainer(const std::vector<T>& vec, const T& value) {
for (const auto& item : vec) {
if (item == value) {
return item;
}
}
return std::nullopt; // Return null-like value
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
auto found = findInContainer(nums, 3);
if (found) {
std::cout << "Found: " << *found << "\n";
}
auto notFound = findInContainer(nums, 10);
if (!notFound) {
std::cout << "Not found\n";
}
return 0;
}
#include <iostream>
#include <memory>
void printIfNotNull(const int* ptr) {
if (ptr != nullptr) {
std::cout << "Value: " << *ptr << "\n";
} else {
std::cout << "Pointer is null\n";
}
}
// More idiomatic C++17
void printIfNotNullModern(const int* ptr) {
if (ptr) { // Implicit conversion to bool
std::cout << "Value: " << *ptr << "\n";
} else {
std::cout << "Pointer is null\n";
}
}
int main() {
int x = 42;
printIfNotNull(&x);
printIfNotNull(nullptr);
return 0;
}
// Bad practices
int* p1 = NULL;
int* p2 = 0;
void* p3 = 0;
// Good practices
int* p4 = nullptr;
void* p5 = nullptr;
// Bad: ambiguous overloading
void process(int value);
void process(const char* value);
// Better: use nullptr_t
void process(int value);
void process(const char* value);
void process(std::nullptr_t);
// Or use pointer consistently
void process(const int* value);
void process(const char* value);
#include <memory>
// Use nullptr with smart pointers
std::unique_ptr<Widget> createWidget() {
if (somethingFailed) {
return nullptr; // Clear intent
}
return std::make_unique<Widget>();
}
// Check with nullptr
auto widget = createWidget();
if (widget != nullptr) {
widget->doSomething();
}
#include <memory>
// Raw pointer - return nullptr for "not found"
int* findIndex(const std::vector<int>& vec, int value) {
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == value) {
return const_cast<int*>(&vec[i]); // Or return index
}
}
return nullptr;
}
// Smart pointer - return nullptr
std::shared_ptr<Widget> getWidgetById(int id) {
if (auto it = cache.find(id); it != cache.end()) {
return it->second;
}
return nullptr;
}
#include <iostream>
#include <optional>
std::pair<int*, bool> findPosition(const std::vector<int>& vec, int value) {
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == value) {
return {const_cast<int*>(&vec[i]), true};
}
}
return {nullptr, false};
}
int main() {
std::vector<int> data = {10, 20, 30};
auto [ptr, found] = findPosition(data, 20);
if (ptr != nullptr) {
std::cout << "Found at position with value: " << *ptr << "\n";
}
return 0;
}
#include <iostream>
#include <optional>
std::optional<int> divide(int a, int b) {
if (b == 0) {
return std::nullopt; // Return null-like value
}
return a / b;
}
int main() {
auto result = divide(10, 2);
if (result) {
std::cout << "Result: " << *result << "\n";
}
auto invalid = divide(10, 0);
if (!invalid) {
std::cout << "Division by zero\n";
}
return 0;
}
// Dangerous - dereferencing nullptr
int* ptr = nullptr;
int value = *ptr; // Undefined behavior!
// Safe - always check
if (ptr != nullptr) {
int value = *ptr;
}
// Don't mix
void* p1 = nullptr;
void* p2 = NULL;
// Always use nullptr consistently
// nullptr is only for pointers
int x = nullptr; // Error!
// For optional integers, use std::optional
std::optional<int> opt; // Empty (null-like)
opt = 42; // Has value
opt = std::nullopt; // Reset to null
  • Always use nullptr instead of NULL or 0 for null pointers
  • nullptr has type std::nullptr_t, avoiding ambiguity
  • Works with all pointer types: raw, smart, function, member
  • Use in function overloading to accept any pointer type
  • Check pointers with ptr != nullptr or just if (ptr)
  • For optional values, consider std::optional (C++17)