C++: Smart Pointers Advanced

Last updated: 2026-08-26

In lesson 45, we learned about move semantics.

Now, we'll dive deep into smart pointers — the core tool for modern C++ memory management.

With smart pointers, you never have to worry about memory leaks again.


1. Smart Pointer Overview

(1) 1.1 What Are Smart Pointers?

Smart pointers are template classes that manage dynamic memory, automatically releasing memory (RAII).

Three types of smart pointers:

Smart Pointer Function Use Case
unique_ptr Exclusive ownership Sole ownership of an object
shared_ptr Shared ownership Multiple places need access to the same object
weak_ptr Weak reference Breaking circular references


2. unique_ptr

(1) 2.1 Basic Usage

unique_ptr is a smart pointer with exclusive ownership — it cannot be copied, only moved.

Example: unique_ptr basic usage (Difficulty ⭐)

▶ Example 2: Code example (Difficulty ⭐)

CPP
#include <iostream>
#include <memory>

int main() {
 std::unique_ptr<int> p1(new int(42));
 std::cout << *p1 << std::endl; // Output: 42
 
 // std::unique_ptr<int> p2 = p1; // ❌ Error! Cannot copy
 std::unique_ptr<int> p2 = std::move(p1); // ✅ Can move
 std::cout << *p2 << std::endl; // Output: 42
 
 return 0;
} // p2 automatically releases memory
▶ Try it Yourself

Output:

TEXT 📖 Display only
(Program output)

(2) 2.2 Custom Deleter

Example: Using unique_ptr to manage a file (Difficulty ⭐⭐)

CPP
#include <iostream>
#include <memory>
#include <cstdio>

// Custom deleter: close file
struct FileDeleter {
 void operator()(FILE* fp) const {
 if (fp) {
 fclose(fp);
 std::cout << "File closed" << std::endl;
 }
 }
};

int main() {
 std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
 // No need to manually fclose, unique_ptr automatically calls FileDeleter
 
 return 0;
}


3. shared_ptr

(1) 3.1 Basic Usage

shared_ptr is a smart pointer with shared ownership, managing memory with reference counting.

Example: shared_ptr basic usage (Difficulty ⭐)

CPP
#include <iostream>
#include <memory>

int main() {
 std::shared_ptr<int> p1 = std::make_shared<int>(42);
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
 
 {
 std::shared_ptr<int> p2 = p1; // Copy, reference count +1
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 2
 } // p2 destroyed, reference count -1
 
 std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
 
 return 0;
}

(2) 3.2 make_shared vs new

Recommended: Use std::make_shared instead of new.

Comparison new make_shared
Exception safety May leak Safe
Performance Two allocations One allocation
Code clarity Verbose Concise
CPP
// Recommended
auto p1 = std::make_shared<int>(42);

// Not recommended
std::shared_ptr<int> p2(new int(42));


4. weak_ptr

(1) 4.1 Why Do We Need weak_ptr?

Problem: shared_ptr can cause circular references, leading to memory leaks.

Example: Circular reference (Difficulty ⭐⭐⭐)

CPP
#include <iostream>
#include <memory>

struct Node {
 std::shared_ptr<Node> next; // Circular reference!
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // Circular reference
 n2->next = n1;
 
 return 0;
} // ❌ n1 and n2 will not be released (memory leak)

(2) 4.2 Breaking Circular References with weak_ptr

Solution: Replace one of the shared_ptr with weak_ptr.

CPP
struct Node {
 std::weak_ptr<Node> next; // Use weak_ptr, does not increase reference count
 ~Node() { std::cout << "Node destroyed" << std::endl; }
};

int main() {
 auto n1 = std::make_shared<Node>();
 auto n2 = std::make_shared<Node>();
 
 n1->next = n2; // Does not increase reference count
 n2->next = n1;
 
 return 0;
} // ✅ Released correctly


5. Smart Pointer Selection Guide

(1) 5.1 How to Choose?

Scenario Recommendation
Exclusive ownership unique_ptr
Shared ownership shared_ptr
Observer weak_ptr or raw pointer
Arrays unique_ptr<T>

(2) 5.2 What Not to Do

Mistake Explanation
Don't initialize multiple shared_ptrs from the same raw pointer Will cause double free
Don't mix raw pointers and smart pointers Breaks RAII
Don't manually call delete Let smart pointers manage it


6. Practice: Managing Resources with Smart Pointers

▶ Example 1: Using smart pointers to manage a database connection (Difficulty ⭐⭐⭐)

TEXT 📖 Display only
#include <iostream>
#include <memory>

// Simulated database connection
class DatabaseConnection {
public:
 DatabaseConnection() {
 std::cout << "Connecting to database" << std::endl;
 }
 
 ~DatabaseConnection() {
 std::cout << "Disconnected" << std::endl;
 }
 
 void query(const std::string& sql) {
 std::cout << "Executing SQL: " << sql << std::endl;
 }
};

int main() {
 // Use unique_ptr to manage exclusive resource
 std::unique_ptrDatabaseConnection conn(new DatabaseConnection());
 conn->query("SELECT * FROM users");
 
 return 0;
} // Automatically disconnects

Output:

TEXT 📖 Display only
Connecting to database
Disconnected
Executing SQL: 

❓ FAQ

Q Which is faster, unique_ptr or shared_ptr?
A unique_ptr is faster (no reference counting overhead). Prefer unique_ptr.

Q When should I use raw pointers?
A - When you don't own the resource (observer) - When interfacing with C libraries - When performance is extremely critical (but usually unnecessary)

Q: Is shared_ptr thread-safe? A: - Reference count modifications are thread-safe - But the pointed-to object is not thread-safe (needs mutex protection)


▶ Example 3: shared_ptr shared ownership (Difficulty ⭐)

CPP
#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Construct" << std::endl; }
    ~Resource() { std::cout << "Destruct" << std::endl; }
};

int main() {
    {
        std::shared_ptr<Resource> p1 = std::make_shared<Resource>();
        std::shared_ptr<Resource> p2 = p1; // Shared ownership

        std::cout << "Reference count: " << p1.use_count() << std::endl;
    }
    // Leaves scope, automatically destructs

    std::cout << "Program ended" << std::endl;
    return 0;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Construct
Destruct
Reference count: 
Program ended
💡 Tip: shared_ptr shares ownership and automatically releases when the reference count reaches zero. Using make_shared is safer for creation.


Key Point Summary
unique_ptr Exclusive ownership, cannot be copied
shared_ptr Shared ownership, reference counting
weak_ptr Weak reference, breaks circular references
make_shared Recommended approach, exception-safe
Selection principle Prefer unique_ptr, use shared_ptr when needed

📖 Summary

📝 Exercises

  1. Basic (Difficulty ⭐): Use unique_ptr to manage a dynamically allocated int, created with make_unique. Try copying the unique_ptr (it should fail to compile), then use move instead.

  2. Intermediate (Difficulty ⭐⭐): Use shared_ptr to implement multiple objects sharing the same resource. Create two shared_ptrs pointing to the same object, and output use_count() to observe reference count changes.

  3. Challenge (Difficulty ⭐⭐⭐): Use weak_ptr to solve a shared_ptr circular reference problem. Create classes A and B that hold shared_ptrs to each other, and observe the memory leak. Then switch to weak_ptr and verify correct release.



Next lesson: Template Metaprogramming Introduction (#47)

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏