std::shared_ptr
In C++, shared_ptr is a smart pointer that manages shared ownership of dynamically allocated memory using reference counting. Multiple shared_ptr instances can point to the same object; the memory is automatically freed when the last owner goes out of scope. Use unique_ptr for single ownership and weak_ptr in combination with shared_ptr to break circular references.
Syntax
// ========================================
// shared_ptr basics
// ========================================
#include <memory>
// Create an object with make_shared (recommended)
// Allocates the control block and object in a single allocation — more efficient
std::shared_ptr<Type> ptr1 = std::make_shared<Type>(constructor args);
// Copying increases the reference count
std::shared_ptr<Type> ptr2 = ptr1; // reference count: 2
// Check the reference count
long count = ptr1.use_count(); // 2
// Use like a regular pointer
ptr1->memberFunction();
*ptr1; // dereference
// Release ownership (count drops; object is freed if count reaches 0)
ptr2.reset(); // reference count back to 1
// Get the raw pointer (ownership is not transferred)
Type* raw = ptr1.get();
// Create a weak reference (does not increase the reference count)
std::weak_ptr<Type> wptr = ptr1;
// Promote to shared_ptr with lock() before using
if (std::shared_ptr<Type> locked = wptr.lock()) {
locked->memberFunction(); // safe to use if valid
}
Syntax Reference
| Syntax / Operation | Description |
|---|---|
| std::make_shared<T>(...) | Dynamically allocates a T object and returns a shared_ptr. More efficient than new because the control block and object share a single allocation. |
| std::shared_ptr<T> p2 = p1 | Copying increases the reference count. p1 and p2 share the same object. |
| p.use_count() | Returns the current reference count (number of owners). Primarily used for debugging. |
| p->member / *p | Accesses members of the managed object. Works just like a regular pointer. |
| p.get() | Returns the raw pointer. Ownership is not transferred. Used to pass to APIs that expect raw pointers. |
| p.reset() | Releases this shared_ptr's ownership. If the count reaches 0, the object is freed. An argument can be passed to replace the managed pointer. |
| if (p) | Evaluates to true if not nullptr. Useful for null checks. |
| std::weak_ptr<T> w = p | Creates a weak reference from a shared_ptr. Does not increase the count — used to break circular references or for "observe only" scenarios. |
| w.lock() | Promotes the weak_ptr to a shared_ptr. Returns nullptr if the object has already been freed. |
| w.expired() | Returns true if the object pointed to by the weak_ptr has been freed. If true, lock() returns nullptr. |
Sample Code
dragonball_shared_ptr.cpp
// ========================================
// dragonball_shared_ptr.cpp
// Manages Dragon Ball Z Fighters with shared_ptr,
// demonstrating reference counting, shared ownership,
// and weak_ptr for circular reference avoidance
// ========================================
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// ========================================
// Warrior — Z Fighter class
// ========================================
class Warrior {
public:
std::string name;
int powerLevel;
Warrior(const std::string& name, int power)
: name(name), powerLevel(power) {
std::cout << "[create] " << name << " allocated." << std::endl;
}
~Warrior() {
std::cout << "[free] " << name << " freed." << std::endl;
}
void showProfile() const {
std::cout << " Warrior: " << name
<< " power: " << powerLevel << std::endl;
}
};
// ========================================
// Ally — holds a weak reference to a partner Warrior
// Using weak_ptr instead of shared_ptr prevents circular references
// ========================================
class Ally {
public:
std::string name;
std::string role;
std::weak_ptr<Warrior> partner;
Ally(const std::string& name, const std::string& role)
: name(name), role(role) {
std::cout << "[create] Ally " << name << " allocated." << std::endl;
}
~Ally() {
std::cout << "[free] Ally " << name << " freed." << std::endl;
}
void showProfile() const {
std::cout << " Ally: " << name
<< " role: " << role << std::endl;
}
void reportToPartner() const {
if (std::shared_ptr<Warrior> w = partner.lock()) {
std::cout << " " << name << " reported to partner " << w->name << "." << std::endl;
} else {
std::cout << " " << name << ": partner has already been freed." << std::endl;
}
}
};
int main() {
// ========================================
// 1. Create objects with make_shared
// ========================================
std::cout << "=== 1. Create with make_shared ===" << std::endl << std::endl;
std::shared_ptr<Warrior> goku =
std::make_shared<Warrior>("Son Goku", 150000000);
std::shared_ptr<Warrior> vegeta =
std::make_shared<Warrior>("Vegeta", 120000000);
std::cout << std::endl;
goku->showProfile();
vegeta->showProfile();
// ========================================
// 2. Copying increases the reference count
// ========================================
std::cout << std::endl << "=== 2. Copy and reference count ===" << std::endl << std::endl;
std::cout << " goku ref count (before copy): " << goku.use_count() << std::endl;
{
std::shared_ptr<Warrior> capsuleRef = goku;
std::shared_ptr<Warrior> lookoutRef = goku;
std::cout << " goku ref count (shared): " << goku.use_count() << std::endl;
capsuleRef->showProfile();
std::cout << " (inner scope ends)" << std::endl;
}
std::cout << " goku ref count (after scope): " << goku.use_count() << std::endl;
// ========================================
// 3. Get the raw pointer with get()
// ========================================
std::cout << std::endl << "=== 3. Raw pointer via get() ===" << std::endl << std::endl;
Warrior* rawPtr = goku.get();
std::cout << " via rawPtr: ";
rawPtr->showProfile();
std::cout << " goku is still valid: " << (goku ? "true" : "false") << std::endl;
// ========================================
// 4. Release ownership with reset()
// ========================================
std::cout << std::endl << "=== 4. Release with reset() ===" << std::endl << std::endl;
std::cout << " vegeta ref count (before reset): " << vegeta.use_count() << std::endl;
vegeta.reset();
std::cout << " vegeta ref count (after reset): " << vegeta.use_count() << std::endl;
std::cout << " vegeta valid: " << (vegeta ? "true" : "false") << std::endl;
// ========================================
// 5. Avoid circular references with weak_ptr
// ========================================
std::cout << std::endl << "=== 5. Circular reference avoidance with weak_ptr ===" << std::endl << std::endl;
std::shared_ptr<Ally> piccolo = std::make_shared<Ally>("Piccolo", "mentor");
std::shared_ptr<Ally> krillin = std::make_shared<Ally>("Krillin", "buddy");
std::cout << std::endl;
piccolo->partner = goku;
krillin->partner = goku;
piccolo->showProfile();
piccolo->reportToPartner();
krillin->showProfile();
krillin->reportToPartner();
std::cout << std::endl;
std::cout << " goku ref count (after weak_ptr set): " << goku.use_count() << std::endl;
// ========================================
// 6. Observe behavior after freeing the object
// ========================================
std::cout << std::endl << "=== 6. weak_ptr behavior after free ===" << std::endl << std::endl;
std::weak_ptr<Warrior> watcherPtr = goku;
std::cout << " before freeing goku: expired = " << (watcherPtr.expired() ? "true" : "false") << std::endl;
goku.reset();
std::cout << std::endl;
std::cout << " after freeing goku: expired = " << (watcherPtr.expired() ? "true" : "false") << std::endl;
piccolo->reportToPartner();
krillin->reportToPartner();
// ========================================
// 7. Manage multiple objects with vector
// ========================================
std::cout << std::endl << "=== 7. Manage with vector<shared_ptr> ===" << std::endl << std::endl;
std::vector<std::shared_ptr<Warrior>> zFighters;
zFighters.push_back(std::make_shared<Warrior>("Son Goku", 150000000));
zFighters.push_back(std::make_shared<Warrior>("Vegeta", 120000000));
zFighters.push_back(std::make_shared<Warrior>("Trunks", 90000000));
std::cout << std::endl;
std::cout << " Z Fighters:" << std::endl;
for (int i = 0; i < (int)zFighters.size(); i++) {
zFighters[i]->showProfile();
}
std::cout << std::endl << "--- main ends. All shared_ptrs are automatically freed ---" << std::endl;
return 0;
}
g++ -std=c++11 dragonball_shared_ptr.cpp -o dragonball_shared_ptr ./dragonball_shared_ptr === 1. Create with make_shared === [create] Son Goku allocated. [create] Vegeta allocated. Warrior: Son Goku power: 150000000 Warrior: Vegeta power: 120000000 === 2. Copy and reference count === goku ref count (before copy): 1 goku ref count (shared): 3 Warrior: Son Goku power: 150000000 (inner scope ends) goku ref count (after scope): 1 === 3. Raw pointer via get() === via rawPtr: Warrior: Son Goku power: 150000000 goku is still valid: true === 4. Release with reset() === vegeta ref count (before reset): 1 [free] Vegeta freed. vegeta ref count (after reset): 0 vegeta valid: false === 5. Circular reference avoidance with weak_ptr === [create] Ally Piccolo allocated. [create] Ally Krillin allocated. Ally: Piccolo role: mentor Piccolo reported to partner Son Goku. Ally: Krillin role: buddy Krillin reported to partner Son Goku. goku ref count (after weak_ptr set): 1 === 6. weak_ptr behavior after free === before freeing goku: expired = false [free] Son Goku freed. after freeing goku: expired = true Piccolo: partner has already been freed. Krillin: partner has already been freed. === 7. Manage with vector<shared_ptr> === [create] Son Goku allocated. [create] Vegeta allocated. [create] Trunks allocated. Z Fighters: Warrior: Son Goku power: 150000000 Warrior: Vegeta power: 120000000 Warrior: Trunks power: 90000000 --- main ends. All shared_ptrs are automatically freed --- [free] Trunks freed. [free] Vegeta freed. [free] Son Goku freed. [free] Ally Krillin freed. [free] Ally Piccolo freed.
Common mistake 1: Calling delete on the raw pointer from get()
Deleting the raw pointer obtained from get() causes a double-free because the shared_ptr will also try to free the same object later, leading to undefined behavior.
// NG:
std::shared_ptr<Warrior> goku = std::make_shared<Warrior>("Son Goku", 150000000);
Warrior* raw = goku.get();
delete raw; // double-free (shared_ptr will also try to free the object)
OK: Use the raw pointer for reading or passing to APIs only — let shared_ptr handle deallocation.
// OK: Warrior* raw = goku.get(); // use for passing to existing APIs raw->showProfile(); // reading is fine // never call delete raw
Common mistake 2: Creating multiple shared_ptrs from the same raw pointer
Creating two separate shared_ptr instances from the same raw pointer gives each its own independent reference count. When one reaches zero it frees the object, leaving the other as a dangling pointer.
// NG:
Warrior* raw = new Warrior("Piccolo", 80000000);
std::shared_ptr<Warrior> sp1(raw); // ref count: 1
std::shared_ptr<Warrior> sp2(raw); // ref count: 1 (separate control block!)
// When sp1 is freed, raw is deleted. sp2 becomes a dangling pointer.
OK: Use make_shared, or copy an existing shared_ptr to share ownership.
// OK:
std::shared_ptr<Warrior> sp1 = std::make_shared<Warrior>("Piccolo", 80000000);
std::shared_ptr<Warrior> sp2 = sp1; // shares the same control block (ref count: 2)
Overview
std::shared_ptr manages shared ownership of an object using reference counting. The count increments on each copy and decrements when a shared_ptr goes out of scope or is reset; when the count reaches zero the object is automatically deleted. Use std::make_shared for construction — it allocates the control block and the object in a single allocation, which is more efficient than using new directly. You can retrieve the raw pointer with get() to pass to existing APIs, but you must never delete that raw pointer. Beware of circular references: if two shared_ptr instances point to each other, the count never reaches zero and the memory leaks. std::weak_ptr solves this — it does not increment the reference count. Always lock() a weak_ptr to obtain a shared_ptr before accessing the object; lock() returns nullptr if the object has already been freed. When single ownership suffices, prefer unique_ptr for its lighter weight and clearer intent.
If you find any errors or copyright issues, please contact us.