Smart Pointers Overview
In C++, smart pointers replace raw pointers to prevent common bugs such as forgotten deallocation and double-free. Since C++11, the standard library <memory> provides three types: unique_ptr, shared_ptr, and weak_ptr, serving "single ownership", "shared ownership", and "circular reference breaking" respectively. This page covers an overview of all three and when to choose each.
Syntax
// ========================================
// Smart pointer basics
// ========================================
#include <memory>
// ----------------------------------------
// unique_ptr — single ownership (no copy, movable)
// ----------------------------------------
// Use make_unique to create an instance (C++14+ recommended)
std::unique_ptr<int> uptr = std::make_unique<int>(42);
// Dereference to read/write
int val = *uptr; // 42
// Transfer ownership with move (uptr becomes nullptr)
std::unique_ptr<int> uptr2 = std::move(uptr);
// uptr is nullptr after the move
// ----------------------------------------
// shared_ptr — shared ownership (reference-counted)
// ----------------------------------------
// Use make_shared to create an instance
std::shared_ptr<int> sptr1 = std::make_shared<int>(100);
// Copying increases the reference count
std::shared_ptr<int> sptr2 = sptr1;
// Check the reference count
long count = sptr1.use_count(); // 2 (shared by sptr1 and sptr2)
// Automatically freed when all shared_ptrs go out of scope
// ----------------------------------------
// weak_ptr — weak reference (does not increase the count)
// ----------------------------------------
std::weak_ptr<int> wptr = sptr1;
// Promote to shared_ptr with lock() before using
if (std::shared_ptr<int> locked = wptr.lock()) {
int v = *locked; // valid — can be accessed
}
// If lock() returns nullptr, the object has already been freed
Syntax Reference
| Syntax / Type | Description |
|---|---|
| std::unique_ptr<T> | Single owner manages the object. Not copyable but movable. Automatically freed when it goes out of scope. |
| std::make_unique<T>(args) | Factory function to safely create a unique_ptr (C++14+). Preferred over new for exception safety. |
| std::move(uptr) | Transfers unique_ptr ownership to another unique_ptr. The source pointer becomes nullptr. |
| std::shared_ptr<T> | Reference-counted shared ownership. Automatically freed when the count reaches zero. |
| std::make_shared<T>(args) | Factory function to safely and efficiently create a shared_ptr. Allocates the control block and the object in a single allocation. |
| sptr.use_count() | Returns the reference count (number of shared owners). Primarily used for debugging. |
| std::weak_ptr<T> | References the object managed by a shared_ptr without increasing the count. Used to break circular references. |
| wptr.lock() | Promotes the weak_ptr to a shared_ptr. Returns nullptr if the object has already been freed. |
| wptr.expired() | Returns true if the object pointed to by the weak_ptr has been freed. If true, lock() returns nullptr. |
Sample Code
smart_pointers.cpp
// ========================================
// smart_pointers.cpp
// Demonstrates the use of unique_ptr,
// shared_ptr, and weak_ptr using Jujutsu
// Kaisen characters
// ========================================
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// ========================================
// Sorcerer class
// ========================================
class Sorcerer {
public:
std::string name;
int grade; // grade (1-4; special grade is 0)
std::string cursedTechnique;
Sorcerer(const std::string& n, int g, const std::string& ct)
: name(n), grade(g), cursedTechnique(ct) {
std::cout << "[create] " << name << " placed in memory." << std::endl;
}
~Sorcerer() {
std::cout << "[free] " << name << " freed from memory." << std::endl;
}
void showInfo() const {
std::string gradeLabel = (grade == 0) ? "Special Grade" : ("Grade " + std::to_string(grade));
std::cout << " " << gradeLabel << " Sorcerer: " << name
<< " / Technique: " << cursedTechnique << std::endl;
}
};
// ========================================
// 1. unique_ptr demo
// Manage a mission assignment with a single owner
// ========================================
void demoUniquePtr() {
std::cout << "=== unique_ptr demo ===" << std::endl;
std::unique_ptr<Sorcerer> itadori =
std::make_unique<Sorcerer>("Itadori Yuji", 4, "Physical Enhancement (Black Flash)");
itadori->showInfo();
// Transfer ownership with move (itadori becomes nullptr)
std::unique_ptr<Sorcerer> mission = std::move(itadori);
std::cout << " After move: itadori is "
<< (itadori == nullptr ? "nullptr" : "valid") << "." << std::endl;
mission->showInfo();
std::cout << " unique_ptr scope ends v" << std::endl;
}
// ========================================
// 2. shared_ptr demo
// Share Gojo Satoru's information across multiple departments
// ========================================
void demoSharedPtr() {
std::cout << std::endl << "=== shared_ptr demo ===" << std::endl;
std::shared_ptr<Sorcerer> gojo =
std::make_shared<Sorcerer>("Gojo Satoru", 0, "Limitless (Blue/Red/Hollow Purple)");
std::cout << " ref count (just created): " << gojo.use_count() << std::endl;
{
std::shared_ptr<Sorcerer> educationDept = gojo;
std::shared_ptr<Sorcerer> combatDept = gojo;
std::cout << " ref count (shared): " << gojo.use_count() << std::endl;
educationDept->showInfo();
}
std::cout << " ref count (after scope): " << gojo.use_count() << std::endl;
std::cout << " shared_ptr scope ends v" << std::endl;
}
// ========================================
// 3. weak_ptr demo
// Observe Fushiguro Megumi without increasing the count
// ========================================
void demoWeakPtr() {
std::cout << std::endl << "=== weak_ptr demo ===" << std::endl;
std::weak_ptr<Sorcerer> watcher;
{
std::shared_ptr<Sorcerer> fushiguro =
std::make_shared<Sorcerer>("Fushiguro Megumi", 2, "Ten Shadows Technique");
watcher = fushiguro; // weak_ptr does not increase the count
std::cout << " while fushiguro is valid: expired = "
<< (watcher.expired() ? "true" : "false") << std::endl;
if (std::shared_ptr<Sorcerer> locked = watcher.lock()) {
locked->showInfo();
}
std::cout << " fushiguro scope ends v" << std::endl;
}
std::cout << " after fushiguro freed: expired = "
<< (watcher.expired() ? "true" : "false") << std::endl;
if (watcher.lock() == nullptr) {
std::cout << " lock() returned nullptr. Safely verified." << std::endl;
}
}
// ========================================
// 4. Container management demo
// Manage multiple sorcerers with vector
// ========================================
void demoContainer() {
std::cout << std::endl << "=== Container management demo ===" << std::endl;
std::vector<std::unique_ptr<Sorcerer>> team;
team.push_back(std::make_unique<Sorcerer>("Kugisaki Nobara", 3, "Straw Doll Technique"));
team.push_back(std::make_unique<Sorcerer>("Okkotsu Yuta", 1, "Rika (Copy)"));
std::cout << " Team members:" << std::endl;
for (int i = 0; i < (int)team.size(); i++) {
team[i]->showInfo();
}
std::cout << " Container scope ends v" << std::endl;
}
int main() {
demoUniquePtr();
demoSharedPtr();
demoWeakPtr();
demoContainer();
std::cout << std::endl << "=== main ends ===" << std::endl;
return 0;
}
g++ -std=c++14 smart_pointers.cpp -o smart_pointers ./smart_pointers === unique_ptr demo === [create] Itadori Yuji placed in memory. Grade 4 Sorcerer: Itadori Yuji / Technique: Physical Enhancement (Black Flash) After move: itadori is nullptr. Grade 4 Sorcerer: Itadori Yuji / Technique: Physical Enhancement (Black Flash) unique_ptr scope ends v [free] Itadori Yuji freed from memory. === shared_ptr demo === [create] Gojo Satoru placed in memory. ref count (just created): 1 ref count (shared): 3 Special Grade Sorcerer: Gojo Satoru / Technique: Limitless (Blue/Red/Hollow Purple) ref count (after scope): 1 shared_ptr scope ends v [free] Gojo Satoru freed from memory. === weak_ptr demo === [create] Fushiguro Megumi placed in memory. while fushiguro is valid: expired = false Grade 2 Sorcerer: Fushiguro Megumi / Technique: Ten Shadows Technique fushiguro scope ends v [free] Fushiguro Megumi freed from memory. after fushiguro freed: expired = true lock() returned nullptr. Safely verified. === Container management demo === [create] Kugisaki Nobara placed in memory. [create] Okkotsu Yuta placed in memory. Team members: Grade 3 Sorcerer: Kugisaki Nobara / Technique: Straw Doll Technique Grade 1 Sorcerer: Okkotsu Yuta / Technique: Rika (Copy) Container scope ends v [free] Okkotsu Yuta freed from memory. [free] Kugisaki Nobara freed from memory. === main ends ===
Common mistake 1: Trying to copy a unique_ptr
unique_ptr is not copyable. Attempting to copy it results in a compile error. To transfer ownership, use std::move.
// NG:
std::unique_ptr<Sorcerer> itadori =
std::make_unique<Sorcerer>("Itadori Yuji", 4, "Physical Enhancement");
std::unique_ptr<Sorcerer> copy = itadori; // compile error — unique_ptr is not copyable
OK: Transfer ownership with std::move. The source becomes nullptr.
// OK: std::unique_ptr<Sorcerer> mission = std::move(itadori); // transfers ownership // itadori is now nullptr
Common mistake 2: Using a weak_ptr without lock()
A weak_ptr cannot be dereferenced directly. Always promote it to a shared_ptr with lock() before accessing the object. If lock() returns nullptr, the object has already been freed.
// NG: std::weak_ptr<Sorcerer> watcher = gojo; watcher->showInfo(); // compile error — weak_ptr cannot be dereferenced directly
OK: Promote to shared_ptr with lock() first.
// OK:
if (std::shared_ptr<Sorcerer> locked = watcher.lock()) {
locked->showInfo(); // access only when valid
}
Overview
Smart pointers use RAII (Resource Acquisition Is Initialization) to automatically free memory in their destructors, preventing memory leaks and double-free errors that are common with raw pointers. unique_ptr guarantees at the type level that there is always exactly one owner, and ownership is explicitly transferred with std::move. shared_ptr maintains an internal reference count so multiple owners can share an object, but "circular references" — where two shared_ptr instances point to each other — cause the count to never reach zero, resulting in a memory leak. weak_ptr solves this by referencing a shared_ptr-managed object without incrementing the count; use lock() to safely access the object. As a general guideline: use unique_ptr for single ownership, shared_ptr for shared ownership, and weak_ptr to break circular references or for "observe only" scenarios. Note that make_unique is available from C++14 and make_shared from C++11.
If you find any errors or copyright issues, please contact us.