Articles

What are Smart Pointers in C++? (With Examples)

C++ smart pointers are wrapper classes that automatically manage the memory lifecycle of dynamically allocated objects. They handle both allocation and deallocation, freeing resources when no longer needed—significantly reducing memory leaks and dangling pointer risks.

Traditional pointers in C++ offer flexibility but require manual memory management. Smart pointers, introduced in C++11, solve this problem through automatic memory management with defined ownership semantics (exclusive, shared, or non-owning).

This guide covers the three types of smart pointers (unique_ptr, shared_ptr, weak_ptr), how to use them effectively, and best practices for modern C++ development.

  • Learn how to use C pointers and proper memory management to make your programs more efficient.
    • Beginner Friendly.
      1 hour
  • References and pointers are some of the most powerful features in C++; they allow programmers to directly manipulate memory.
    • Beginner Friendly.
      1 hour

What are smart pointers in C++?

Smart pointers have ownership semantics, meaning they keep track of which pointer “owns” a particular resource and when that ownership should end. Depending on the type of smart pointer we use, ownership rules can differ—allowing exclusive access (unique_ptr), shared access (shared_ptr), or non-owning observation (weak_ptr).

Here’s an example to illustrate how C++ smart pointers work:

#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr = make_unique<int>(10); // Smart pointer manages memory
cout << *ptr << endl; // Output: 10 (Accessing value)
return 0; // Memory automatically released
}

In this example, we use a unique_ptr, one of the most commonly used smart pointers in C++. The pointer ptr owns the integer value 10, and when the main() function ends, the unique_ptr automatically deletes the allocated memory—no manual delete is needed.

Now that we’ve got an idea of what C++ smart pointers are, let’s explore their different types and how they function.

Types of smart pointers in C++

There are three main types of smart pointers in C++, each designed to handle ownership and lifetime management differently:

  • unique_ptr
  • shared_ptr
  • weak_ptr

Let’s discuss each of these types in detail.

1. unique_ptr

The unique_ptr is the simplest type of smart pointer. It provides exclusive ownership of a dynamically allocated object — meaning that no other unique_ptr can point to the same resource. Once a unique_ptr goes out of scope, it automatically destroys the managed object.

Key characteristics:

  • Only one unique_ptr can own a resource at a time
  • Ownership can be transferred using std::move()
  • Cannot be copied, ensuring clear ownership semantics

Example:

This example demonstrates how unique_ptr works in C++:

#include <memory>
#include <iostream>
using namespace std;
int main() {
unique_ptr<int> uPtr = make_unique<int>(25);
cout << "Value: " << *uPtr << endl;
// Transfer ownership using move semantics
unique_ptr<int> uPtr2 = move(uPtr);
if (!uPtr)
cout << "uPtr no longer owns the resource." << endl;
return 0;
}

Here, uPtr transfers ownership to uPtr2 using std::move(). After the transfer, uPtr becomes null.

The output will be:

Value: 25
uPtr no longer owns the resource.

2. shared_ptr

The shared_ptr is a reference-counted smart pointer that allows multiple pointers to share ownership of the same resource. It keeps track of how many shared_ptr instances point to a particular object using an internal reference counter.

Key characteristics:

  • Multiple shared_ptr objects can point to the same resource
  • The object is destroyed automatically when the last shared_ptr goes out of scope
  • Provides the method use_count() to check how many owners exist

Example:

Here is an example that demonstrates how shared_ptr works in C++:

#include <memory>
#include <iostream>
using namespace std;
int main() {
shared_ptr<int> sPtr1 = make_shared<int>(100);
shared_ptr<int> sPtr2 = sPtr1; // Shared ownership
cout << "Count: " << sPtr1.use_count() << endl;
sPtr2.reset(); // Decrease the reference count
cout << "Count after reset: " << sPtr1.use_count() << endl;
return 0;
}

This example shows how shared_ptr automatically handles reference counting. When the last shared_ptr owning the object is destroyed, the memory is released.

The output will be:

Count: 2
Count after reset: 1

3. weak_ptr

A weak_ptr provides a non-owning “weak” reference to an object managed by a shared_ptr. It’s primarily used to break circular references — a situation where two shared_ptr objects reference each other, preventing proper deallocation.

Key characteristics:

  • It doesn’t increase the reference count of the object
  • Must be converted to a shared_ptr before accessing the object
  • Commonly used in observer patterns and graph-like structures

Example:

This example demonstrates how weak_ptr works in C++:

#include <memory>
#include <iostream>
using namespace std;
int main() {
shared_ptr<int> sPtr = make_shared<int>(50);
weak_ptr<int> wPtr = sPtr;
cout << "Use count: " << sPtr.use_count() << endl;
if (auto temp = wPtr.lock()) {
cout << "Value: " << *temp << endl;
} else {
cout << "Object no longer exists." << endl;
}
return 0;
}

Here, wPtr.lock() temporarily converts the weak_ptr into a shared_ptr to safely access the managed object without affecting its lifetime.

The output will be:

Use count: 1
Value: 50

After understanding the types, let’s see how to use smart pointers effectively in C++.

How to use smart pointers in C++

Using C++ smart pointers is straightforward once we understand their ownership semantics. The goal of using them is to manage dynamically allocated resources safely without manually calling new or delete.

Creating smart pointers

The recommended way to create C++ smart pointers is by using the factory functions make_unique and make_shared:

make_unique<T>(args...) // For unique_ptr 
make_shared<T>(args...) // For shared_ptr 

These functions allocate memory safely and handle exceptions correctly.

Here is an example:

#include <iostream>
#include <memory>
using namespace std;
int main() {
auto uPtr = make_unique<int>(42);
auto sPtr = make_shared<string>("Hello Smart Pointers");
cout << *uPtr << endl;
cout << *sPtr << endl;
return 0;
}

In this example, both smart pointers are created without manually allocating memory using new.

The output will be:

42
Hello Smart Pointers

Unlike unique_ptr and shared_ptr, a weak_ptr cannot be created directly using factory functions. This is because weak_ptr does not own the memory—it merely observes an object managed by a shared_ptr.

To create a weak_ptr, we must first have a shared_ptr and then assign it to a weak_ptr:

#include <iostream>
#include <memory>
using namespace std;
int main() {
auto sPtr = make_shared<int>(100);
weak_ptr<int> wPtr = sPtr; // Create a weak_ptr from a shared_ptr
cout << "shared_ptr use count: " << sPtr.use_count() << endl;
// Lock weak_ptr to safely access the managed object
if (auto temp = wPtr.lock()) {
cout << "weak_ptr value: " << *temp << endl;
} else {
cout << "Object no longer exists." << endl;
}
return 0;
}

Here, the weak_ptr acts as a non-owning reference to the object handled by the shared_ptr. It doesn’t affect the reference count but allows safe access through the lock() method.

The output will be:

shared_ptr use count: 1
weak_ptr value: 100

Transferring ownership

When using unique_ptr, ownership can be transferred but not copied. This ensures only one pointer owns a resource at any time:

#include <iostream>
#include <memory>
using namespace std;
void show(unique_ptr<int> ptr) {
cout << "Value: " << *ptr << endl;
}
int main() {
unique_ptr<int> num = make_unique<int>(99);
show(move(num)); // Transfer ownership to function
if (!num)
cout << "num is now null (ownership transferred)." << endl;
}

This prevents double-deletion errors and enforces clear ownership.

The output will be:

Value: 99
num is now null (ownership transferred).

Sharing ownership among multiple pointers

If multiple parts of a program need to share the same resource, shared_ptr is ideal. It uses reference counting to manage ownership safely:

#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> sPtr1 = make_shared<int>(10);
shared_ptr<int> sPtr2 = sPtr1; // Shared ownership
cout << "Reference count: " << sPtr1.use_count() << endl;
sPtr2.reset();
cout << "Reference count after reset: " << sPtr1.use_count() << endl;
}

Here, the object is deleted only when both sPtr1 and sPtr2 go out of scope.

The output will be:

Reference count: 2
Reference count after reset: 1

Avoiding circular references with weak_ptr

When two shared pointers reference each other, they create a circular dependency, preventing memory deallocation. To solve this, one of the relationships should be a weak_ptr:

#include <iostream>
#include <memory>
using namespace std;
struct Node {
shared_ptr<Node> next;
weak_ptr<Node> prev; // Breaks circular reference
~Node() { cout << "Node destroyed\n"; }
};
int main() {
auto first = make_shared<Node>();
auto second = make_shared<Node>();
first->next = second;
second->prev = first;
cout << "Nodes linked successfully.\n";
}

Without the weak_ptr, both nodes would stay alive indefinitely, causing a memory leak.

The output will be:

Nodes linked successfully.
Node destroyed
Node destroyed

With usage covered, let’s go through the advantages and disadvantages of C++ smart pointers.

Advantages and disadvantages of smart pointers

C++ smart pointers offer several advantages:

  • Automatic memory management: Reduces risk of memory leaks.
  • Exception safety: Automatically deallocates memory even during exceptions.
  • Improved readability: Code becomes more concise and safer.
  • Ownership semantics: Clarifies which part of the code owns a resource.

However, there are some disadvantages as well:

  • Overhead: Slightly more memory and computation due to reference counting.
  • Circular references: Possible with shared_ptr unless handled with weak_ptr.
  • Not always necessary: For simple stack variables, smart pointers add unnecessary complexity.

Let’s now see how smart pointers differ from traditional pointers in C++.

Pointers vs smart pointers

Here are the differences between pointers and smart pointers in C++:

Feature Pointers Smart Pointers
Memory management Manual Automatic
Header file None required <memory>
Ownership Not defined Well-defined (unique_ptr / shared_ptr / weak_ptr)
Memory safety Low High
Deallocation Must be done manually using delete Automatic on scope exit

Finally, let’s go through some of the best practices for using C++ smart pointers.

Best practices for using smart pointers

Apply these best practices to make the most out of smart pointers in C++:

  • Prefer make_unique and make_shared over raw new for safety and clarity
  • Avoid using shared_ptr unnecessarily — use unique_ptr when possible
  • Break circular references using weak_ptr
  • Do not mix traditional and smart pointers for the same object

Following these best practices will ensure effective usage of C++ smart pointers.

Conclusion

In this guide, we discussed C++ smart pointers in detail, covering what they are, their types, and how to use them for different ownership needs. We also explored their advantages and disadvantages, compared them with traditional pointers, and went through some of the best practices for using them efficiently.

C++ smart pointers provide a modern, safe, and efficient way to manage dynamic memory. By using them, we can write cleaner, more reliable code that automatically handles resource management. While they don’t eliminate all memory risks, C++ smart pointers significantly reduce the chances of leaks and undefined behavior.

If you want to learn more about pointers in C++, check out the Learn C++: References and Pointers course on Codecademy.

Frequently asked questions

1. What is the use of smart pointers in C++?

C++ smart pointers are used to automatically manage the lifetime of dynamically allocated objects, ensuring that memory is released when it’s no longer needed.

2. Is C++ memory safe with smart pointers?

While smart pointers make C++ much safer by preventing leaks and dangling pointers, they don’t guarantee complete memory safety. Developers must still handle circular references and avoid misuse.

3. Should I only use smart pointers in C++?

Not always. Use C++ smart pointers for dynamic memory and traditional pointers or references for stack-based or non-owning relationships.

4. What are the key characteristics of C++ smart pointers?

Here are the key characteristics of C++ smart pointers:

  • Defined ownership models: Provide explicit ownership semantics — exclusive (std::unique_ptr), shared (std::shared_ptr), or weak/non-owning (std::weak_ptr).
  • RAII-based design: Follow the Resource Acquisition Is Initialization (RAII) principle, ensuring resource cleanup when objects go out of scope.
  • Type-safe templates: Implemented as class templates, enforcing compile-time type checking and reducing pointer misuse.
  • Interoperability with traditional pointers: Can seamlessly integrate with legacy code while offering safer memory handling.

5. What is the difference between smart pointers and normal pointers?

Normal pointers require manual memory management, while smart pointers automatically handle allocation and deallocation, reducing the risk of leaks.

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team

Learn more on Codecademy

  • Learn how to use C pointers and proper memory management to make your programs more efficient.
    • Beginner Friendly.
      1 hour
  • References and pointers are some of the most powerful features in C++; they allow programmers to directly manipulate memory.
    • Beginner Friendly.
      1 hour
  • Learn about the C programming language in this beginner-friendly skill path.
    • Includes 6 Courses
    • With Certificate
    • Beginner Friendly.
      11 hours