READMEC++

README

Pointers Memory / Smart Pointers

Concept Lesson
Intermediate
4 min

Learning Objective

Understand Pointers Memory well enough to explain it, recognize it in C++, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

MemoryTable Of ContentsWhy Smart PointersThe Problem With Raw PointersThe Solution: Raii With Smart Pointers
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
2 min read18 headings

Smart Pointers in C++

Table of Contents

  1. Why Smart Pointers
  2. unique_ptr
  3. shared_ptr
  4. weak_ptr
  5. Custom Deleters
  6. Best Practices

Why Smart Pointers

The Problem with Raw Pointers

void riskyFunction() {
    int* data = new int[1000];

    // What if processData() throws?
    processData(data);

    // What if we return early?
    if (someCondition) {
        return;  // MEMORY LEAK!
    }

    delete[] data;  // Might never reach here
}

The Solution: RAII with Smart Pointers

Smart pointers automatically manage memory through RAII:

  • Acquire resource in constructor
  • Release resource in destructor
  • No manual delete needed
#include <memory>

void safeFunction() {
    auto data = make_unique<int[]>(1000);

    processData(data.get());  // Even if throws, destructor runs

    if (someCondition) {
        return;  // No leak! unique_ptr destructor frees memory
    }
}  // Automatically deleted here

Three Smart Pointer Types

TypeOwnershipUse Case
unique_ptrExclusiveSingle owner, most common
shared_ptrSharedMultiple owners
weak_ptrNon-owningBreaking cycles, caching

unique_ptr

The most common and efficient smart pointer.

Basic Usage

#include <memory>

// Create unique_ptr
unique_ptr<int> p1 = make_unique<int>(42);     // Preferred
unique_ptr<int> p2(new int(42));               // Direct construction

// Access the value
cout << *p1 << endl;      // Dereference
cout << p1.get() << endl; // Get raw pointer

// Reset to new value
p1.reset(new int(100));   // Delete old, point to new
p1.reset();               // Delete and become nullptr

// Check if pointing to something
if (p1) {
    cout << "p1 is valid" << endl;
}

Arrays with unique_ptr

// Array specialization
unique_ptr<int[]> arr = make_unique<int[]>(10);
arr[0] = 42;  // Bracket access

// Automatically calls delete[]

Move Semantics (No Copying!)

unique_ptr<int> p1 = make_unique<int>(42);

// CANNOT copy
// unique_ptr<int> p2 = p1;  // ERROR!

// CAN move
unique_ptr<int> p2 = move(p1);  // p1 is now nullptr

Common Operations

unique_ptr<int> ptr = make_unique<int>(42);

ptr.get();        // Raw pointer (don't delete!)
*ptr;             // Dereference
ptr.reset();      // Delete and set to nullptr
ptr.reset(new_p); // Delete and point to new
ptr.release();    // Release ownership, return raw ptr
ptr = nullptr;    // Same as reset()
if (ptr) { }      // Check if valid

Transfer Ownership

unique_ptr<Widget> createWidget() {
    return make_unique<Widget>();  // Move on return
}

void takeOwnership(unique_ptr<Widget> w) {
    // Widget deleted when function ends
}

unique_ptr<Widget> w = createWidget();
takeOwnership(move(w));  // w is now nullptr

shared_ptr

For shared ownership - multiple pointers can own the same resource.

Basic Usage

#include <memory>

// Create shared_ptr
shared_ptr<int> sp1 = make_shared<int>(42);  // Preferred
shared_ptr<int> sp2(new int(42));            // Direct

// Copy is allowed! (increases reference count)
shared_ptr<int> sp3 = sp1;
shared_ptr<int> sp4 = sp1;

cout << sp1.use_count() << endl;  // 3 (sp1, sp3, sp4)

Reference Counting

{
    shared_ptr<int> p1 = make_shared<int>(42);  // count = 1
    cout << "Count: " << p1.use_count() << endl;

    {
        shared_ptr<int> p2 = p1;  // count = 2
        shared_ptr<int> p3 = p1;  // count = 3
        cout << "Count: " << p1.use_count() << endl;
    }  // p2, p3 destroyed, count = 1

    cout << "Count: " << p1.use_count() << endl;
}  // p1 destroyed, count = 0, memory freed

Common Operations

shared_ptr<int> ptr = make_shared<int>(42);

ptr.get();        // Raw pointer
*ptr;             // Dereference
ptr.use_count();  // Number of owners
ptr.unique();     // true if use_count() == 1
ptr.reset();      // Decrease count, maybe delete
ptr = nullptr;    // Same as reset()
if (ptr) { }      // Check if valid

Arrays with shared_ptr (C++17+)

// C++17 and later
shared_ptr<int[]> arr = make_shared<int[]>(10);
arr[0] = 42;

// Pre-C++17: use custom deleter
shared_ptr<int> arr(new int[10], default_delete<int[]>());

make_shared vs new

// Preferred: make_shared
auto p1 = make_shared<Widget>(args);

// Avoid: new
shared_ptr<Widget> p2(new Widget(args));

Why prefer make_shared:

  1. One memory allocation (instead of two)
  2. Exception safe
  3. More efficient

weak_ptr

Non-owning observer of shared_ptr. Doesn't affect reference count.

Purpose

  1. Break circular references
  2. Caching - observe without keeping alive
  3. Observer pattern - safely check if object still exists

Basic Usage

shared_ptr<int> sp = make_shared<int>(42);
weak_ptr<int> wp = sp;  // Doesn't increase count

cout << sp.use_count() << endl;  // Still 1

// Must convert to shared_ptr to use
if (auto locked = wp.lock()) {
    cout << *locked << endl;  // Safe to use
} else {
    cout << "Object destroyed" << endl;
}

Detecting Expiration

shared_ptr<int> sp = make_shared<int>(42);
weak_ptr<int> wp = sp;

cout << wp.expired() << endl;  // false

sp.reset();  // Object destroyed

cout << wp.expired() << endl;  // true

Breaking Circular References

Without weak_ptr (memory leak):

struct Node {
    shared_ptr<Node> next;  // Circular!
    shared_ptr<Node> prev;  // Memory leak
};

With weak_ptr (correct):

struct Node {
    shared_ptr<Node> next;
    weak_ptr<Node> prev;  // Won't keep alive
};

Example: Observer Pattern

class Subject {
    vector<weak_ptr<Observer>> observers;

public:
    void notify() {
        for (auto& wp : observers) {
            if (auto sp = wp.lock()) {
                sp->update();
            }
        }
    }
};

Custom Deleters

Customize how resources are freed.

With unique_ptr

// Lambda deleter
auto deleter = [](int* p) {
    cout << "Custom delete" << endl;
    delete p;
};
unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);

// Function pointer deleter
void myDeleter(FILE* f) {
    if (f) fclose(f);
}
unique_ptr<FILE, decltype(&myDeleter)> file(fopen("test.txt", "r"), myDeleter);

With shared_ptr

// Easier - deleter is type-erased
shared_ptr<int> ptr(new int(42), [](int* p) {
    cout << "Custom delete" << endl;
    delete p;
});

// File example
shared_ptr<FILE> file(fopen("test.txt", "r"), fclose);

Practical Examples

// Managing C resources
shared_ptr<SDL_Window> window(
    SDL_CreateWindow(...),
    SDL_DestroyWindow
);

// Array with shared_ptr (pre-C++17)
shared_ptr<int> arr(new int[10], default_delete<int[]>());

// Custom pool allocator
auto poolDeleter = [&pool](Widget* w) { pool.deallocate(w); };
unique_ptr<Widget, decltype(poolDeleter)> w(pool.allocate(), poolDeleter);

Best Practices

✅ Do

// 1. Use make_unique/make_shared
auto p1 = make_unique<Widget>();
auto p2 = make_shared<Widget>();

// 2. Pass by value to transfer ownership
void takeOwnership(unique_ptr<Widget> w);

// 3. Pass by reference to use without ownership
void useWidget(const Widget& w);
void useWidget(Widget* w);  // If nullable

// 4. Return unique_ptr from factories
unique_ptr<Widget> createWidget() {
    return make_unique<Widget>();
}

// 5. Use weak_ptr for breaking cycles
struct Node {
    shared_ptr<Node> next;
    weak_ptr<Node> prev;
};

// 6. Check weak_ptr before use
if (auto sp = wp.lock()) {
    // safe to use sp
}

❌ Don't

// 1. Don't use raw new with smart pointers (exception safety)
shared_ptr<Widget> p(new Widget());  // OK but not ideal
auto p = make_shared<Widget>();      // Better

// 2. Don't call delete on smart pointer's raw pointer
Widget* raw = ptr.get();
delete raw;  // WRONG! Double delete

// 3. Don't create multiple smart pointers from raw pointer
Widget* raw = new Widget();
shared_ptr<Widget> p1(raw);
shared_ptr<Widget> p2(raw);  // WRONG! Double delete

// 4. Don't use shared_ptr when unique_ptr suffices
// unique_ptr is more efficient

// 5. Don't return reference to local smart pointer's content
Widget& createWidget() {
    auto p = make_unique<Widget>();
    return *p;  // DANGLING!
}

Guidelines Summary

  1. Prefer unique_ptr - most efficient, clearest ownership
  2. Use shared_ptr only when truly needed
  3. Use weak_ptr to break cycles
  4. Always use make_unique/make_shared
  5. Pass unique_ptr by value to transfer ownership
  6. Pass by reference/pointer when not taking ownership
  7. Return unique_ptr from factory functions

Comparison

FeatureRaw Pointerunique_ptrshared_ptr
OwnershipNoneExclusiveShared
CopyableYesNoYes
MovableYesYesYes
OverheadNoneNoneReference count
Thread-safeNoNoCount is atomic
Use caseNon-owningSingle ownerMultiple owners

Quick Reference

#include <memory>

// unique_ptr
unique_ptr<T> p = make_unique<T>(args);
unique_ptr<T[]> arr = make_unique<T[]>(size);
p.get();              // Raw pointer
p.reset();            // Delete and nullify
p.release();          // Release ownership
unique_ptr<T> p2 = move(p);  // Transfer ownership

// shared_ptr
shared_ptr<T> p = make_shared<T>(args);
p.use_count();        // Reference count
p.unique();           // true if count == 1
shared_ptr<T> p2 = p; // Copy (increments count)

// weak_ptr
weak_ptr<T> wp = sp;
wp.expired();         // True if object gone
wp.lock();            // Get shared_ptr (or nullptr)

// Common patterns
auto p = make_unique<Widget>();       // Factory
takeOwnership(move(p));               // Transfer
useWidget(*p);                        // Use by ref

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue