Handwritten Smart Pointer: Understanding the Underlying Logic of C++ Memory Management

From the public account: Program Meow Master

Memory management has always been a core and complex topic in C++. While raw pointers are straightforward to use, they are prone to issues such as memory leaks and double deletions. The emergence of smart pointers is a powerful tool in modern C++ to enhance code safety and maintainability.

Here, we will handwrite a simple smart pointer, helping you truly understand how smart pointers work.

The Dilemma of Raw Pointers

First, let’s look at a typical raw pointer code:

void foo() {
    MyClass* p = new MyClass();
    // Business logic
    delete p;  // Memory leak if forgotten
}

If <span>delete p</span> is forgotten, the program will leak memory; if it is written twice, the program may crash.

Manual management of raw pointers is not only cumbersome but also prone to subtle errors.

Design Goals of Smart Pointers

Smart pointers are essentially a class that encapsulates raw pointers and are responsible for:

  • Automatically releasing resources to prevent memory leaks
  • Clearly defining resource ownership to prevent double deletions
  • Simplifying operations while supporting access similar to raw pointers

Handwriting a Unique Smart Pointer — SimpleSmartPtr

We will first implement a unique smart pointer that exclusively owns the pointer resource, disallowing copying and only allowing moving.

template<typename T>
class SimpleSmartPtr {
    T* ptr;

public:
    // Constructor, default nullptr
    explicit SimpleSmartPtr(T* p = nullptr) : ptr(p) {}

    // Destructor, releases resources
    ~SimpleSmartPtr() {
        delete ptr;
    }

    // Disable copy constructor and copy assignment to prevent double deletion
    SimpleSmartPtr(const SimpleSmartPtr<>) = delete;
    SimpleSmartPtr& operator=(const SimpleSmartPtr<>) = delete;

    // Support move construction, transferring resource ownership
    SimpleSmartPtr(SimpleSmartPtr<&> other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // Support move assignment, transferring resource ownership
    SimpleSmartPtr& operator=(SimpleSmartPtr<&> other) noexcept {
        if (this != &other) {
            delete ptr;         // Release old resource
            ptr = other.ptr;    // Take over new resource
            other.ptr = nullptr; // Nullify old pointer
        }
        return *this;
    }

    // Dereference operator, supports pointer operations
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }

    // Get raw pointer
    T* get() const { return ptr; }
};

Key Points Explained:

  • Constructor accepts a raw pointer, defaulting to <span>nullptr</span>.
  • Destructor ensures that resources are automatically released when the pointer is destructed.
  • Copy prohibition: Prevents multiple smart pointers from managing the same resource, avoiding double deletions.
  • Move support: Allows the transfer of resource ownership between smart pointers, preventing resource waste.
  • Operator overloading: Makes the smart pointer behave like a regular pointer for ease of use.

Why Prohibit Copying? Why Support Moving?

If copying is allowed:

SimpleSmartPtr<MyClass> p1(new MyClass());
SimpleSmartPtr<MyClass> p2 = p1;  // Copy, both smart pointers point to the same raw pointer

Both smart pointers will attempt to <span>delete</span> the same pointer during destruction, leading to double deletion and program crashes.

Move semantics allow for safe transfer of resource ownership between smart pointers:

SimpleSmartPtr<MyClass> p2 = std::move(p1);  // p2 takes over the resource, p1 is nullified

This avoids double deletion and allows for flexible resource transfer.

The Role of weak_ptr

When two objects hold each other via <span>shared_ptr</span><span>, it creates a </span><strong><span>circular reference</span></strong><span>, causing the count to never reach zero, leading to resource leaks.</span>

<span>weak_ptr</span><span> is a type of smart pointer that does not increase the reference count, specifically used to observe </span><code><span>shared_ptr</span><span> and avoid circular references.</span>

Conclusion

By handwriting smart pointers, you can not only:

  • Understand the RAII (Resource Acquisition Is Initialization) concept
  • Master the importance of move semantics and copy prohibition in design
  • Comprehend resource ownership and lifecycle management
  • Understand the basic principles of reference-counted smart pointers

It will also enable you to write safer, more efficient, and maintainable modern C++ code.

—END—

Leave a Comment