Advertisement
Verified Partner
Enterprise Cloud Infrastructure, GPU Clusters & AI APIs
Deploy high-throughput inference and quant trading pipelines with ultra-low latency.
Explore Platform β†’

Modern C++ Memory Management: std::unique_ptr, std::shared_ptr & RAII

By Elena Rostova β€’ Advanced β€’ 13 min read β€’ Updated 2026-09-11

What You Will Master in This Tutorial

  • Understand unique ownership with std::unique_ptr and zero-cost abstraction.

1. The Power of std::unique_ptr and Move Semantics

std::unique_ptr automatically deallocates memory when it goes out of scope.

CPP
#include <iostream>
#include <memory>

void runWorker() {
    auto conn = std::make_unique<int>(42);
    // Destructor called automatically when out of scope
}
Note: Always prefer std::make_unique over new.
Advertisement
Verified Partner
Quantitative Trading Systems & 30 AI Business Blueprints
Build predictable monthly recurring revenue with retainers & automated bots.
View Blueprints β†’

Knowledge Check: Test Your Understanding

1. What is the runtime performance overhead of std::unique_ptr compared to a raw pointer?

Frequently Asked Questions

When should I use std::shared_ptr instead of std::unique_ptr?
Only when an asset truly requires multiple owners across different lifetimes or asynchronous threads.

2. Step by step: refactor a raw-pointer class to smart pointers

Theory becomes habit the first time you refactor a class that owns a heap object by hand. Here is the classic pre-modern shape β€” a Car that news an Engine and deletes it in the destructor:

CPP
class Engine {
public:
    void start() {}
};

class Car {
    Engine* engine;                       // owning raw pointer
public:
    Car() : engine(new Engine) {}
    ~Car() { delete engine; }             // manual cleanup

    // Compiler-generated copy operations copy the POINTER,
    // so two Cars delete the same Engine. Double free.
};

The bug is not the delete; it is that ownership is invisible. Nothing in the type says Car owns the Engine, so the compiler happily generates copy operations that duplicate the obligation. The refactor makes ownership the type:

CPP
#include <memory>
#include <utility>

class Engine {
public:
    void start() {}
};

class Car {
    std::unique_ptr<Engine> engine;       // ownership is now visible
public:
    Car() : engine(std::make_unique<Engine>()) {}

    // unique_ptr is move-only, so Car is move-only too:
    // copies are deleted by the compiler, moves transfer ownership.
    Car(Car&&) noexcept = default;
    Car& operator=(Car&&) noexcept = default;

    Engine& getEngine() { return *engine; }  // observer access, no ownership
};

Three rules finish the refactor anywhere you apply it. Factory functions return std::unique_ptr so the transfer of ownership is in the signature. Functions that only look at an object take a reference or a raw pointer obtained with .get() β€” observing is free and claims nothing. And a class that holds a unique_ptr almost never needs a handwritten destructor again: the Rule of Zero replaces the Rule of Five.

3. A complete worked example

A texture cache for a game is the canonical shared-ownership problem: many sprites share one loaded texture, the texture must unload when the last user lets go, and the cache itself must not keep everything alive forever. That combination β€” shared_ptr for users, weak_ptr for the cache β€” is the whole design:

CPP
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

struct Texture {
    std::string path;
    explicit Texture(std::string p) : path(std::move(p)) {
        std::cout << "Loaded " << path << "\n";
    }
    ~Texture() { std::cout << "Unloaded " << path << "\n"; }
};

class TextureCache {
public:
    std::shared_ptr<Texture> load(const std::string& path) {
        if (auto it = cache_.find(path); it != cache_.end()) {
            if (auto tex = it->second.lock()) {   // still alive? reuse it
                return tex;
            }
        }
        auto tex = std::make_shared<Texture>(path);
        cache_[path] = tex;                        // cache observes via weak_ptr
        return tex;
    }

private:
    std::unordered_map<std::string, std::weak_ptr<Texture>> cache_;
};

int main() {
    TextureCache cache;
    {
        auto hero = cache.load("hero.png");        // Loaded hero.png
        auto heroAgain = cache.load("hero.png");   // reused, no reload
        std::cout << "Same object: " << (hero == heroAgain) << "\n";
    }   // last shared_ptr dies -> Unloaded hero.png

    auto bg = cache.load("bg.png");   // weak_ptr had expired -> fresh load
    return 0;
}

Run it and the output teaches the semantics: hero.png loads once although load() was called twice, unloads exactly when the inner scope closes, and the cache never extends a texture’s life by a single instruction β€” because a weak_ptr observes without owning. Swap the cache to shared_ptr and every texture lives until the cache dies; that one-word difference is the most common real-world shared_ptr design error.

Step 3: hand ownership out of factory functions β€” and handle non-memory resources

Factories complete the pattern: a function that creates an object returns it as a unique_ptr, so the caller receives ownership without ever seeing a raw new. The same mechanism manages resources that are not memory at all. A C file handle has no destructor, so you supply one β€” a custom deleter β€” and RAII does the rest:

CPP
#include <cstdio>
#include <memory>

struct FileCloser {
    void operator()(std::FILE* f) const {
        if (f) std::fclose(f);
    }
};

// A unique_ptr whose "delete" is fclose
using UniqueFile = std::unique_ptr<std::FILE, FileCloser>;

UniqueFile openLogFile(const char* path) {
    return UniqueFile(std::fopen(path, "a"));   // ownership moves to caller
}

void appendLine(const char* path, const char* line) {
    auto file = openLogFile(path);              // closes on every exit path,
    if (!file) return;                          // including exceptions
    std::fputs(line, file.get());               // .get(): observe, don't own
}

This is the deepest reason the deep-dive below tells you to design ownership first: once ownership is a type, files, sockets, and database handles all get the same one-line cleanup guarantee as heap objects β€” no finally blocks, no forgotten close on the error path.

4. Troubleshooting

Going deeper: smart pointers in practice

Knowing the syntax of std::unique_ptr and std::shared_ptr is only half the skill β€” the other half is developing instincts for ownership design. In production code, smart pointers shine when ownership is decided at the design stage: factory functions return std::unique_ptr to hand over ownership unambiguously, long-lived managers hold std::shared_ptr only for genuinely shared resources, and everything else is passed as a raw pointer or reference that simply observes. Teams that adopt this discipline find that entire classes of bugs β€” leaks, double frees, use-after-free β€” stop appearing in code review, because the type signatures themselves document who is responsible for every object, and the compiler enforces what the documentation used to merely promise. The result is code that is shorter and safer at the same time, because allocation and release stop being separate decisions scattered across a file.

Common mistakes

Frequently asked questions

Should I ever use new and delete directly in modern C++?

Almost never in application code. std::make_unique and std::make_shared build the smart pointer without a naked new in your code, which keeps ownership explicit and exception-safe from the very first line.

What is std::weak_ptr actually for?

A weak_ptr observes an object managed by shared_ptr without extending its lifetime. It is the standard fix for ownership cycles, and the right tool for caches and registries that must check whether an object still exists without keeping it alive.

Do smart pointers replace RAII, or are they RAII?

They are RAII applied to memory: the pointer object's destructor releases the resource automatically at scope exit, exactly the way a lock guard releases a mutex or a file wrapper closes its handle.

Get 1 AI agent blueprint every week

Join the free PVT newsletter: one practical AI agent or automation you can build and sell, every week. No spam.