Managing Dynamic Memory Safely with C++ Smart Pointers

Raw pointers require explicit deallocation before a program ends, otherwise memory leaks occur. Manual free calls are tedious and error-prone. C++11 introduced smart pointers to automate this process. The primary types are std::shared_ptr, std::unique_ptr, and std::weak_ptr, all defined in <memory>.

The Reference Counting Model

Reference counting prevents leaks by tracking the number of active references to a dynamically allocated object. Acquiring a new reference increments the counter, and releasing one decrements it. When the counter hits zero, the associated heap memory is automatically released.

Shared Ownership via std::shared_ptr

std::shared_ptr manages an object through shared ownership. It maintains a reference count reflecting how many shared_ptr instances point to the same object, eliminating manual delete. To avoid raw new, prefer std::make_shared.

#include <iostream>
#include <memory>

void addOne(std::shared_ptr<int> val) {
    (*val)++;
}

int main() {
    auto sp = std::make_shared<int>(20);
    addOne(sp);
    std::cout << *sp << std::endl;   // prints 21
}

Member functions provide additional control: get() yields the raw pointer, use_count() reports the current reference count, and reset() decrements the count and detaches the shared_ptr.

#include <iostream>
#include <memory>

int main() {
    auto p1 = std::make_shared<int>(50);
    auto p2(p1);
    auto p3 = p1;

    std::cout << "p1: " << p1.use_count() << ", p2: " << p2.use_count()
              << ", p3: " << p3.use_count() << std::endl;

    p3.reset();
    std::cout << "After p3.reset() -> p1: " << p1.use_count()
              << ", p2: " << p2.use_count()
              << ", p3: " << p3.use_count() << std::endl;

    p2.reset();
    std::cout << "After p2.reset() -> p1: " << p1.use_count()
              << ", p2: " << p2.use_count()
              << ", p3: " << p3.use_count() << std::endl;
}

Exclusive Ownership with std::unique_ptr

std::unique_ptr enforces exclusive ownership. It forbids copying to other smart pointers, safeguarding against unintended sharing. Use std::make_unique for construction.

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> up1 = std::make_unique<int>(100);
    // std::unique_ptr<int> up2 = up1;           // compiler error
    std::unique_ptr<int> up2 = std::move(up1);   // transfers ownership
    std::cout << *up2 << std::endl;
}

Although copying is disallowed, ownership can be transferred via std::move. After the move, the source pointer becomes null and the destination assumes sole responsibility.

#include <iostream>
#include <memory>

class Demo {
public:
    Demo()  { std::cout << "Demo constructed\n"; }
    ~Demo() { std::cout << "Demo destroyed\n"; }
    void greet() const { std::cout << "Hello from Demo\n"; }
};

void show(const Demo& obj) {
    std::cout << "Inside show()\n";
}

int main() {
    std::unique_ptr<Demo> a = std::make_unique<Demo>();
    if (a) a->greet();

    {
        std::unique_ptr<Demo> b(std::move(a));
        show(*b);
        if (b) b->greet();
        if (a) a->greet();    // a is null, so this is skipped

        a = std::move(b);     // return ownership to a
        if (b) b->greet();    // b is now null
        std::cout << "b going out of scope\n";
    }

    if (a) a->greet();        // a still owns the object
}

Breaking Cycles with std::weak_ptr

While shared_ptr solves many leak scenarios, cyclic dependencies can prevent deallocation. For example, two objects holding shared_ptr to each other never reach a zero reference count. std::weak_ptr is a non‑owning observer: it references an object managed by a shared_ptr without increasing the reference count, allowing cycles to be broken.

A weak_ptr lacks operator* and operator->. To acces the managed object, use lock(), which returns a valid shared_ptr if the object still exists, or a null pointer otherwise.

#include <iostream>
#include <memory>

struct Node;

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // weak reference to avoid cycle
};

int main() {
    auto first = std::make_shared<Node>();
    auto second = std::make_shared<Node>();

    first->next = second;
    second->prev = first;      // uses weak_ptr

    // Both shared_ptrs will be correctly destroyed when
    // first and second go out of scope.
}

weak_ptr is typically used alongside shared_ptr to inspect the validity of a resource without extending its lifetime.

Tags: C++ smart pointers Memory Management RAII shared_ptr

Posted on Tue, 11 Aug 2026 16:37:52 +0000 by nelson201