CODE: CPP Smart Pointers

In modern C++, memory management is no longer about manually calling new and delete. It is about designing ownership explicitly so that lifetime, safety, performance, and concurrency remain predictable and secure.

Before smart pointers, we must understand where objects live.

Stack Memory

Allocated automatically.

Destroyed automatically.

cpp
void foo() {
    int x = 10;        // stack
    std::string s = "Hello"; // stack
}

When foo() exits → x and s are destroyed.

md
Call foo()

+------------------+
| return address   |
|------------------|
| int x = 10       |
|------------------|
| std::string s    |
+------------------+

Return → POP → destroyed

Key points:

  • No manual cleanup
  • Cache friendly
  • Deterministic destruction
  • Cannot control lifetime beyond scope

Heap Memory

Allocated manually (or via smart pointers).

Lifetime must be managed.

cpp
int* p = new int(42);
delete p;

If you forget delete p; → memory leak.

md
Stack:                Heap:

p  --------->   +-----------+
                 |  42       |
                 +-----------+

Key points:

  • Flexible lifetime
  • Dynamic size
  • Manual delete required
  • Easy to leak or double free

Object Lifetime

Object lifetime begins at construction and ends at destruction. On the stack, this is automatic. On the heap, this must be enforced explicitly.

Lifetime = from construction to destruction.

Stack object:

cpp
{
    std::string s = "Hi";
} // destructor called here

Heap object:

cpp
std::string* s = new std::string("Hi");
// destructor NOT called unless delete

That difference is the root of 30+ years of C++ bugs.

Key concept:

  • Stack → lifetime tied to scope
  • Heap → lifetime tied to ownership logic

RAII — The Core Philosophy

Resource Acquisition Is Initialization

RAII ensures that resource lifetime is tied directly to object lifetime. This is the single most important concept in modern C++ memory safety.

Resource lifetime == object lifetime.

If an object owns a resource:

  • Acquire in constructor
  • Release in destructor

Example: File wrapper

cpp
class File {
    FILE* f;

public:
    File(const char* name) {
        f = fopen(name, "r");
    }

    ~File() {
        if (f) fclose(f);
    }
};

If an object acquires a resource in its constructor, it must release it in its destructor.

Usage:

cpp
void read() {
    File file("data.txt");
} // automatically closed here

  • No leaks
  • Exception safe
  • Deterministic cleanup

Key principles:

  • Deterministic cleanup
  • Exception safety
  • No resource leaks

This is the foundation of smart pointers.


Pointers & References

Pointers and references are fundamental language tools, but they do not express ownership clearly by themselves.

Raw Pointers — Why They’re Dangerous

Raw pointers are simply memory addresses. They do not encode ownership rules.

cpp
int* p = new int(5);
delete p;
*p = 10;  // Undefined behavior (dangling pointer)

Problems

  • Memory leaks
  • Double delete
  • Dangling pointer
  • Ownership unclear
  • Hard to reason in multithreaded systems

Dangling Pointer Diagram

md
delete p;

p  ------X----->  freed memory

In embedded / secure systems → this becomes a vulnerability.

Problems:

  • Memory leaks
  • Dangling pointers
  • Double delete
  • Ownership ambiguity
  • Hard to reason in multithreaded systems

In security-sensitive systems, use-after-free becomes an exploit vector.

References — Why They’re Limited

References are safer than raw pointers because they cannot be null and cannot be reseated.

cpp
int x = 10;
int& ref = x;

  • Cannot be null
  • Cannot be reseated

But:

  • No ownership
  • Cannot represent optional object
  • Cannot manage dynamic lifetime

References are aliases, not owners.


Smart Pointers

Smart pointers implement RAII for heap memory.

Smart pointers bring RAII to heap memory. They encode ownership directly into the type system, eliminating entire classes of memory bugs.

They answer explicitly:

  • Who owns this object?
  • When is it destroyed?
  • Can ownership be shared?

std::unique_ptr

Exclusive ownership. Only one owner exists.

cpp
#include <memory>

std::unique_ptr<int> p = std::make_unique<int>(42);

Ownership Rule

md
unique_ptr  ---->  object

If unique_ptr dies → object dies.

Move Semantics

cpp
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = std::move(p1);

After move:

md
p1 = null
p2 ----> object

  • Zero overhead
  • Deterministic
  • Safe
  • Best default choice

std::shared_ptr

Shared ownership. Reference counted.

cpp
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;

Reference Count Diagram

md
p1  \
      ----> object (count = 2)
p2  /

When count reaches 0 → object destroyed.

Cost

  • Control block
  • Atomic ref counting
  • More memory
  • Slower

In high-performance systems → use carefully.

std::weak_ptr

Non-owning observer of shared_ptr. Prevents circular references.

cpp
std::weak_ptr<int> wp = p1;

if (auto sp = wp.lock()) {
    std::cout << *sp;
}

Weak pointer does not increase ref count.

Key points:

  • Breaks cycles
  • Safe access via lock()
  • Slight runtime overhead

The Circular Reference Problem

cpp
struct B;

struct A {
    std::shared_ptr<B> b;
};

struct B {
    std::shared_ptr<A> a;
};

Graph:

md
A <----> B

Ref count never reaches zero → memory leak.

Fix with weak_ptr

cpp
struct B {
    std::weak_ptr<A> a;
};

Now:

md
A ----> B
^       |
|       v
weak   shared


Full Example — Proper Ownership Design

cpp
#include <memory>
#include <iostream>

class Sensor {
public:
    void read() { std::cout << "Reading\n"; }
};

class Controller {
    std::unique_ptr<Sensor> sensor;

public:
    Controller()
        : sensor(std::make_unique<Sensor>()) {}

    void process() {
        sensor->read();
    }
};

int main() {
    Controller c;
    c.process();
}

Ownership graph:

cpp
Controller
    |
    +---- unique_ptr ----> Sensor

Clear. Deterministic. Safe.


Summary

md
Stack  → automatic lifetime
Heap   → explicit ownership required
RAII   → lifetime == scope
Smart pointers → RAII for heap