Nullptr
nullptr and Modern Type Safety
Section titled “nullptr and Modern Type Safety”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.
The Problem with NULL and 0
Section titled “The Problem with NULL and 0”In C++, NULL is typically defined as either 0 or ((void*)0), both of which cause problems.
Ambiguity in Overloading
Section titled “Ambiguity in Overloading”#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;}Type Safety Issues
Section titled “Type Safety Issues”#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 Type
Section titled “nullptr Type”nullptr is a keyword representing a null pointer constant of type std::nullptr_t.
Basic Usage
Section titled “Basic Usage”#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;}std::nullptr_t
Section titled “std::nullptr_t”#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;}nullptr in Function Templates
Section titled “nullptr in Function Templates”Handling Null Pointers in Templates
Section titled “Handling Null Pointers in Templates”#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 pointerstemplate<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;}Conditional Return Types
Section titled “Conditional Return Types”#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;}nullptr vs nullptr_t
Section titled “nullptr vs nullptr_t”Explicit Null Check
Section titled “Explicit Null Check”#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++17void 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;}Best Practices
Section titled “Best Practices”Always Use nullptr
Section titled “Always Use nullptr”// Bad practicesint* p1 = NULL;int* p2 = 0;void* p3 = 0;
// Good practicesint* p4 = nullptr;void* p5 = nullptr;In Function Declarations
Section titled “In Function Declarations”// Bad: ambiguous overloadingvoid process(int value);void process(const char* value);
// Better: use nullptr_tvoid process(int value);void process(const char* value);void process(std::nullptr_t);
// Or use pointer consistentlyvoid process(const int* value);void process(const char* value);In Smart Pointers
Section titled “In Smart Pointers”#include <memory>
// Use nullptr with smart pointersstd::unique_ptr<Widget> createWidget() { if (somethingFailed) { return nullptr; // Clear intent } return std::make_unique<Widget>();}
// Check with nullptrauto widget = createWidget();if (widget != nullptr) { widget->doSomething();}In Return Statements
Section titled “In Return Statements”#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 nullptrstd::shared_ptr<Widget> getWidgetById(int id) { if (auto it = cache.find(id); it != cache.end()) { return it->second; } return nullptr;}nullptr in Modern C++ Code
Section titled “nullptr in Modern C++ Code”Structured Bindings (C++17)
Section titled “Structured Bindings (C++17)”#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;}std::optional (C++17)
Section titled “std::optional (C++17)”#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;}Common Pitfalls
Section titled “Common Pitfalls”Forgetting nullptr Checks
Section titled “Forgetting nullptr Checks”// Dangerous - dereferencing nullptrint* ptr = nullptr;int value = *ptr; // Undefined behavior!
// Safe - always checkif (ptr != nullptr) { int value = *ptr;}Mixing NULL and nullptr
Section titled “Mixing NULL and nullptr”// Don't mixvoid* p1 = nullptr;void* p2 = NULL;
// Always use nullptr consistentlyUsing nullptr with non-pointers
Section titled “Using nullptr with non-pointers”// nullptr is only for pointersint x = nullptr; // Error!
// For optional integers, use std::optionalstd::optional<int> opt; // Empty (null-like)opt = 42; // Has valueopt = std::nullopt; // Reset to nullKey Takeaways
Section titled “Key Takeaways”- Always use
nullptrinstead ofNULLor0for null pointers nullptrhas typestd::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 != nullptror justif (ptr) - For optional values, consider
std::optional(C++17)