Question

What Are Smart Pointers in C++ and When Should You Use Them?

cpppointersc++11smart-pointersc++-faq

Question

What is a smart pointer in C++, and when should it be used?

I want to understand what smart pointers are, how they differ from raw pointers, and in which situations I should use them in real C++ programs.

Short Answer

By the end of this page, you will understand what smart pointers are in C++, why they help prevent memory-management bugs, and when to choose std::unique_ptr, std::shared_ptr, or std::weak_ptr. You will also see practical examples, common mistakes, and a small project that shows how automatic resource management works.

Concept

Smart pointers are C++ objects that act like pointers but also manage the lifetime of dynamically allocated memory automatically.

In older C++ code, developers often used raw pointers with new and delete:

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

This works, but it is easy to make mistakes:

  • forgetting to call delete
  • calling delete twice
  • using a pointer after the object has already been deleted
  • leaking memory when exceptions occur

Smart pointers solve this by tying ownership of a resource to an object whose destructor performs cleanup automatically. This idea follows RAII: Resource Acquisition Is Initialization.

The main standard smart pointers in C++ are:

  • std::unique_ptr — one owner
  • std::shared_ptr — multiple shared owners
  • std::weak_ptr — non-owning reference to an object managed by shared_ptr

Why this matters in real programming:

  • it makes memory management safer
  • it reduces leaks
  • it makes ownership clearer in code
  • it works correctly even when exceptions happen
  • it improves maintainability in larger codebases

In modern C++, smart pointers should usually be preferred over manual new and delete.

Mental Model

Think of a smart pointer like a keycard system for a room:

  • A raw pointer is like writing the room number on paper. You know where the room is, but nobody is responsible for locking it or cleaning it up.
  • A unique_ptr is like one person holding the only keycard. When that person leaves, the room is closed properly.
  • A shared_ptr is like several people sharing access. The room stays open until the last person leaves.
  • A weak_ptr is like a visitor pass that lets you check whether the room still exists, but it does not keep the room open.

The key idea is ownership. Smart pointers are not just about pointing to something. They answer the question: who is responsible for cleaning this up?

Take Quiz

Syntax and Examples

Core syntax

std::unique_ptr

#include <memory>

std::unique_ptr<int> p = std::make_unique<int>(42);
  • Owns one object exclusively
  • Cannot be copied
  • Can be moved
  • Deletes the object automatically when it goes out of scope

std::shared_ptr

#include <memory>

std::shared_ptr<int> p = std::make_shared<int>(42);
std::shared_ptr<int> q = p;
  • Multiple smart pointers can share ownership
  • The object is deleted only when the last shared_ptr is destroyed

std::weak_ptr

#include <memory>

std::shared_ptr<int> p = std::make_shared<int>(42);
std::weak_ptr<int> w = p;
  • Does not own the object
  • Used to observe an object managed by shared_ptr
  • Helps avoid circular ownership problems

Beginner-friendly example

#include <iostream>
#include <memory>

class File {
public:
    File() {
        std::cout << "File opened\n";
    }

    ~File() {
        std::cout << "File closed\n";
    }

    void read() {
        std::cout << "Reading file\n";
    }
};

int main() {
    std::unique_ptr<File> file = std::make_unique<File>();
    file->read();

    return 0;
}

What this shows

  • file owns the File object
  • You use -> just like with a normal pointer
  • When main ends, file is destroyed
  • Its destructor automatically deletes the File object
  • The File destructor runs without needing delete

This is safer than manually writing new File() and delete.

Step by Step Execution

Consider this example:

#include <iostream>
#include <memory>

class Item {
public:
    Item() {
        std::cout << "Item created\n";
    }

    ~Item() {
        std::cout << "Item destroyed\n";
    }
};

int main() {
    {
        std::unique_ptr<Item> ptr = std::make_unique<Item>();
        std::cout << "Using item\n";
    }

    std::cout << "Block ended\n";
}

Step by step:

  1. std::make_unique<Item>() creates an Item object.

  2. That object is owned by ptr.

  3. The constructor prints:

    Item created
    
  4. The next line prints:

    Using item
    
  5. Execution reaches the end of the inner block { ... }.

  6. ptr goes out of scope.

  7. Because ptr is a unique_ptr, its destructor deletes the Item automatically.

  8. The Item destructor prints:

    Item destroyed
    
  9. Then the program continues and prints:

    Block ended
    

Expected output:

Item created
Using item
Item destroyed
Block ended

This is the main benefit of smart pointers: cleanup happens automatically when ownership ends.

Real World Use Cases

Smart pointers are used whenever a program needs clear, safe ownership of dynamically allocated objects.

Common use cases

Managing objects created at runtime

auto user = std::make_unique<User>();

Useful when an object should exist only while a scope or owner exists.

Returning heap-allocated objects from functions

std::unique_ptr<Connection> createConnection() {
    return std::make_unique<Connection>();
}

This makes ownership explicit: the caller receives the connection and owns it.

Storing polymorphic objects

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Rectangle>());

This is very common in C++ because base-class objects often need pointer-based storage.

Shared resources across parts of an application

auto config = std::make_shared<Config>();
serviceA.setConfig(config);
serviceB.setConfig(config);

Use this when several parts of the program truly need shared ownership.

Caches, observers, and graph-like structures

std::weak_ptr is often used when one object needs to reference another without extending its lifetime.

Example:

  • a UI widget observing a shared model
  • a cache storing references without forcing objects to stay alive
  • parent/child relationships that would otherwise create cycles
Take Quiz

Real Codebase Usage

In real projects, smart pointers are mostly about expressing ownership clearly.

Common patterns

Prefer unique_ptr by default

Many codebases follow this rule:

  • use stack objects when possible
  • if dynamic allocation is needed, use std::unique_ptr
  • use std::shared_ptr only when shared ownership is truly required

Factory functions

std::unique_ptr<Logger> createLogger() {
    return std::make_unique<Logger>();
}

This makes ownership transfer obvious.

Polymorphism in containers

std::vector<std::unique_ptr<Plugin>> plugins;
plugins.push_back(std::make_unique<AudioPlugin>());

This avoids slicing and ensures cleanup happens automatically.

Guarding optional resources

class Service {
private:
    std::unique_ptr<Connection> connection;
};

If connection is present, it is owned by the service. If not, it can be nullptr.

Shared application state

auto session = std::make_shared<Session>();
controller.setSession(session);
view.setSession(session);

This is common in systems where several objects depend on the same long-lived resource.

Breaking cycles with weak_ptr

If two objects own each other with shared_ptr, they may never be deleted. One side is usually changed to weak_ptr.

Practical advice

  • Prefer std::make_unique and std::make_shared
  • Avoid naked new in application code
  • Pass raw pointers or references when you only need access, not ownership
  • Use smart pointers to model ownership, not just to replace every pointer blindly

Common Mistakes

1. Using shared_ptr when unique_ptr is enough

Beginners often choose shared_ptr because it feels more flexible.

std::shared_ptr<int> p = std::make_shared<int>(10);

This is not always wrong, but it adds reference counting and weaker ownership clarity.

Better:

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

Use shared_ptr only when multiple owners are really needed.

2. Mixing raw new and manual delete with smart pointers

Broken example:

std::unique_ptr<int> p(new int(5));
delete p.get();

This is wrong because unique_ptr will later try to delete the same memory again.

Avoid this by letting the smart pointer handle deletion.

Correct:

auto p = std::make_unique<int>(5);

3. Creating multiple shared_ptrs from the same raw pointer

Broken example:

int* raw = new int(7);
std::shared_ptr<int> a(raw);
std::shared_ptr<int> b(raw);

This creates two separate owners that both think they should delete the same object.

Correct:

auto a = std::make_shared<int>(7);
std::shared_ptr<int> b = a;

4. Forgetting that unique_ptr cannot be copied

Broken example:

std::unique_ptr<int> a = std::make_unique<int>(1);
std::unique_ptr<int> b = a;

This fails because unique_ptr has exclusive ownership.

Correct:

std::unique_ptr<int> a = std::make_unique<int>(1);
std::unique_ptr<int> b = std::move(a);

After the move, a no longer owns the object.

5. Causing circular references with shared_ptr

Broken pattern:

struct B;

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

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

If A and B point to each other this way, neither may be destroyed.

Fix one side with weak_ptr:

struct B;

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

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

6. Using a smart pointer when no dynamic allocation is needed

Sometimes a plain stack variable is better:

int value = 42;

Do not use smart pointers unless dynamic lifetime or ownership is actually needed.

Comparisons

Smart pointer types compared

TypeOwnershipCopyableAutomatically deletes?Main use
T* raw pointerNone by itselfYesNoNon-owning access or low-level code
std::unique_ptr<T>ExclusiveNoYesSingle owner
std::shared_ptr<T>SharedYesYesMultiple owners
std::weak_ptr<T>Non-owning observerYesNoObserve shared_ptr object safely

unique_ptr vs shared_ptr

Featureunique_ptrshared_ptr
OwnershipOne ownerMany owners
PerformanceUsually lighterMore overhead due to reference counting
CopyingNot allowedAllowed
Best default choiceYesOnly when needed

Smart pointers vs stack allocation

ApproachWhen to use
Stack objectWhen lifetime is local and fixed
unique_ptrWhen object lifetime must outlive current scope or ownership must move
shared_ptrWhen several parts of code must jointly keep object alive

Raw pointer vs smart pointer

A raw pointer can still be useful when it does not own the object:

void printUser(const User* user);

Here, user is just being accessed, not owned. Smart pointers are mainly for ownership management.

Take Quiz

Cheat Sheet

Quick reference

Include header

#include <memory>

Create a unique_ptr

auto p = std::make_unique<MyClass>();

Create a shared_ptr

auto p = std::make_shared<MyClass>();

Create a weak_ptr

std::weak_ptr<MyClass> w = p;

Access the object

p->method();
(*p).method();

Move a unique_ptr

auto b = std::move(a);

Check for null

if (p) {
    p->method();
}

Get raw pointer without transferring ownership

MyClass* raw = p.get();

Important rules

  • Prefer stack allocation when possible
  • Prefer std::unique_ptr by default
  • Use std::shared_ptr only for real shared ownership
  • Use std::weak_ptr to break cycles or observe shared objects
  • Prefer std::make_unique and std::make_shared
  • Avoid manual delete
  • Do not create multiple shared_ptrs from the same raw pointer

Edge cases

  • unique_ptr cannot be copied
  • moved-from unique_ptr becomes empty
  • weak_ptr must be converted with lock() before use

Example:

if (auto s = w.lock()) {
    s->method();
}

FAQ

What is the main purpose of a smart pointer in C++?

A smart pointer automatically manages the lifetime of dynamically allocated objects, reducing memory leaks and ownership bugs.

When should I use unique_ptr instead of shared_ptr?

Use unique_ptr when only one part of the program should own the object. This is the best default choice in modern C++.

Are raw pointers still used in modern C++?

Yes. Raw pointers are still useful for non-owning access, especially in function parameters and low-level code.

Why is make_unique preferred over new?

It is clearer, safer, and avoids manual memory management. It also makes ownership obvious.

What problem does weak_ptr solve?

It prevents circular ownership with shared_ptr and lets you observe an object without keeping it alive.

Do smart pointers only manage memory?

Mostly they are used for dynamic memory, but the underlying idea is automatic resource management through RAII.

Can I put smart pointers in containers like vector?

Yes. This is very common, especially std::vector<std::unique_ptr<Base>> for polymorphic objects.

Take Quiz

Mini Project

Description

Build a small C++ program that manages a collection of shapes using smart pointers. This demonstrates a common real-world pattern: storing polymorphic objects safely without manual delete calls.

Goal

Create and use dynamically allocated objects with std::unique_ptr, store them in a container, and let C++ clean them up automatically.

Requirements

  • Create a base class named Shape with a virtual method draw().
  • Create at least two derived classes such as Circle and Square.
  • Store the objects in a std::vector<std::unique_ptr<Shape>>.
  • Loop through the vector and call draw() on each object.
  • Do not use manual new or delete.
Take Quiz

Keep learning

Related questions

View more