Modern C++

Smart Pointers

Use modern C++ smart pointers to manage memory safely and automatically.

The Problem with Raw Pointers

Manual memory management with new and delete is error-prone:

  • Forget to delete → memory leak
  • Delete twice → undefined behavior
  • Use after delete → use-after-free bug

Smart Pointers (C++11)

Smart pointers automatically manage memory using RAII (Resource Acquisition Is Initialization):

  • unique_ptr: Sole ownership, automatically deleted when out of scope
  • shared_ptr: Shared ownership, deleted when last owner goes out of scope
  • weak_ptr: Non-owning reference to a shared_ptr (breaks circular references)

The Rule: Prefer Smart Pointers

In modern C++, you should rarely use raw new/delete. Use smart pointers instead.

Example

cpp
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Resource {
    string name;
public:
    Resource(const string& n) : name(n) {
        cout << "Acquired: " << name << endl;
    }
    ~Resource() {
        cout << "Released: " << name << endl;
    }
    void use() { cout << "Using: " << name << endl; }
};

void demonstrateUniquePtr() {
    // unique_ptr - sole ownership
    unique_ptr<Resource> res = make_unique<Resource>("FileHandle");
    res->use();

    // Transfer ownership with move
    unique_ptr<Resource> res2 = move(res);
    // res is now null
    if (!res) cout << "res is empty after move" << endl;
    res2->use();
    // Automatically deleted when res2 goes out of scope
}

void demonstrateSharedPtr() {
    shared_ptr<Resource> r1 = make_shared<Resource>("SharedDB");
    cout << "Count: " << r1.use_count() << endl;  // 1
    {
        shared_ptr<Resource> r2 = r1;  // shared ownership
        cout << "Count: " << r1.use_count() << endl;  // 2
        r2->use();
    }  // r2 destroyed, count goes to 1
    cout << "Count: " << r1.use_count() << endl;  // 1
}  // r1 destroyed, Resource released

int main() {
    cout << "--- unique_ptr ---" << endl;
    demonstrateUniquePtr();

    cout << "--- shared_ptr ---" << endl;
    demonstrateSharedPtr();

    // unique_ptr in containers
    vector<unique_ptr<Resource>> resources;
    resources.push_back(make_unique<Resource>("Res1"));
    resources.push_back(make_unique<Resource>("Res2"));

    for (auto& r : resources) r->use();
    // All automatically freed when vector is destroyed

    return 0;
}
Example — CPP
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Resource {
    string name;
public:
    Resource(const string& n) : name(n) {
        cout << "Acquired: " << name << endl;
    }
    ~Resource() {
        cout << "Released: " << name << endl;
    }
    void use() { cout << "Using: " << name << endl; }
};

void demonstrateUniquePtr() {
    // unique_ptr - sole ownership
    unique_ptr<Resource> res = make_unique<Resource>("FileHandle");
    res->use();

    // Transfer ownership with move
    unique_ptr<Resource> res2 = move(res);
    // res is now null
    if (!res) cout << "res is empty after move" << endl;
    res2->use();
    // Automatically deleted when res2 goes out of scope
}

void demonstrateSharedPtr() {
    shared_ptr<Resource> r1 = make_shared<Resource>("SharedDB");
    cout << "Count: " << r1.use_count() << endl;  // 1
    {
        shared_ptr<Resource> r2 = r1;  // shared ownership
        cout << "Count: " << r1.use_count() << endl;  // 2
        r2->use();
    }  // r2 destroyed, count goes to 1
    cout << "Count: " << r1.use_count() << endl;  // 1
}  // r1 destroyed, Resource released

int main() {
    cout << "--- unique_ptr ---" << endl;
    demonstrateUniquePtr();

    cout << "--- shared_ptr ---" << endl;
    demonstrateSharedPtr();

    // unique_ptr in containers
    vector<unique_ptr<Resource>> resources;
    resources.push_back(make_unique<Resource>("Res1"));
    resources.push_back(make_unique<Resource>("Res2"));

    for (auto& r : resources) r->use();
    // All automatically freed when vector is destroyed

    return 0;
}
Run it yourself — save as main.cpp
$ g++ main.cpp -o main && ./main

Where You'll See This in the Real World

Smart pointers answer a question every long-running program has to answer: who is responsible for destroying this, and when. Different answers show up in different parts of real systems:

  • One clear owner — a window owns its widgets, a document owns its pages, a connection owns its buffer. When the owner goes, the owned thing goes with it. This is the common case and the reason unique_ptr is the default.
  • Shared ownership with an unpredictable last user — a texture used by several models, a config read by several threads, a parsed file held by several requests. Nobody knows who finishes last, so the object is destroyed when the final holder lets go.
  • Observing without owning — a cache that should not keep an entry alive on its own, or a child that refers back to its parent. Two objects that hold each other with shared ownership will never be destroyed at all; the non-owning reference is what breaks that cycle.

The detail that bites in production is that reference counting is not free: every copy of a shared pointer touches an atomic counter, and passing one by value in a hot loop is a measurable cost for ownership nobody wanted. Real code passes a reference to the object, and reserves the shared pointer for the moment ownership genuinely changes hands.