In modern C++ programming, smart pointers provides automatic memory management. Among these smart pointers, shared_ptr
is widely used because it takes care of releasing memory when it is no longer needed, thanks to reference counting. However, misusing shared_ptr
can lead to issues like memory leaks, especially in complex data structures such as binary trees. Let’s explore the proper use of shared_ptr
and how weak_ptr
can help resolve certain problems.
Case Study: Creating Binary Tree with shared_ptr

Consider a simple binary tree implementation:
#include <iostream> #include <memory> class Node { int value; public: std::shared_ptr<Node> leftPtr; std::shared_ptr<Node> rightPtr; Node(int val) : value(val) { std::cout << "Constructor" << std::endl; } ~Node() { std::cout << "Destructor" << std::endl; } }; int main() { std::shared_ptr<Node> root = std::make_shared<Node>(4); root->leftPtr = std::make_shared<Node>(2); root->rightPtr = std::make_shared<Node>(5); return 0; }
Output:
Constructor Constructor Constructor Destructor Destructor Destructor
Here, the constructors and destructors are called correctly, ensuring that memory is properly managed and released.
Problem: Introducing Parent Pointers
When we add a parent pointer to each node:
#include <iostream> #include <memory> class Node { int value; public: std::shared_ptr<Node> leftPtr; std::shared_ptr<Node> rightPtr; std::shared_ptr<Node> parentPtr; Node(int val) : value(val) { std::cout << "Constructor" << std::endl; } ~Node() { std::cout << "Destructor" << std::endl; } }; int main() { std::shared_ptr<Node> ptr = std::make_shared<Node>(4); ptr->leftPtr = std::make_shared<Node>(2); ptr->leftPtr->parentPtr = ptr; ptr->rightPtr = std::make_shared<Node>(5); ptr->rightPtr->parentPtr = ptr; std::cout << "ptr reference count = " << ptr.use_count() << std::endl; std::cout << "ptr->leftPtr reference count = " << ptr->leftPtr.use_count() << std::endl; std::cout << "ptr->rightPtr reference count = " << ptr->rightPtr.use_count() << std::endl; return 0; }
Output:
Constructor Constructor Constructor ptr reference count = 3 ptr->leftPtr reference count = 1 ptr->rightPtr reference count = 1
Now the constructor will be called three times, but there will be no call to the destructor, and that indicates a memory leak.
Frequently Asked:
The reason for this problem with shared_ptr
is cyclic references, i.e.,
If two objects refer to each other using shared_ptr
s, then no one will delete the internal memory when they go out of scope.
It happens because shared_ptr
in its destructor, after decrementing the reference count of the associated memory, checks if the count is 0; then it deletes that memory. If it’s greater than 1, it means that another shared_ptr
is using this memory.
But in this kind of scenario, these shared_ptr
s will always find the count greater than 0 in the destructor.
Let’s reconfirm this for the above example:
When ptr’s destructor is called,
- It decrements the reference count by 1.
- Then it checks if the current count is 0, but that is 2 because both the left and right children have a
shared_ptr
object referencing the parent, i.e.,ptr
. - The Left and Right children will be deleted only when the memory for
ptr
will be deleted, but that’s not going to happen because the reference count is greater than 0. - Hence, the memory for neither
ptr
nor its children will be deleted. Therefore, no destructor was called.
Solution: Using weak_ptr Smart Pointer
To solve the cyclic reference problem, we can replace the shared_ptr
for the parent pointer with a weak_ptr
. This allows us to share but not own the object, breaking the cycle:
std::weak_ptr<Node> parentPtr;
A weak_ptr
doesn’t contribute to the reference count, hence it avoids creating strong reference cycles.
Here’s how you can work with weak_ptr
:
std::shared_ptr<int> ptr = std::make_shared<int>(4); std::weak_ptr<int> weakPtr(ptr); std::shared_ptr<int> ptr_2 = weakPtr.lock(); if (ptr_2) { std::cout << (*ptr_2) << std::endl; std::cout << "Reference Count :: " << ptr_2.use_count() << std::endl; } if (weakPtr.expired() == false) { std::cout << "Not expired yet" << std::endl; }
Output:
4 Reference Count :: 2 Not expired yet
The lock()
function tries to retrieve a shared_ptr
that owns the object. If the original shared_ptr
is gone, lock()
will return an empty shared_ptr
.
Improved Binary Tree Example
Applying weak_ptr
to our binary tree, we can avoid memory leaks even with parent pointers:
#include <iostream> #include <memory> class Node { int value; public: std::shared_ptr<Node> leftPtr; std::shared_ptr<Node> rightPtr; std::weak_ptr<Node> parentPtr; // Changed from shared_ptr to weak_ptr Node(int val) : value(val) { std::cout << "Constructor" << std::endl; } ~Node() { std::cout << "Destructor" << std::endl; } }; int main() { std::shared_ptr<Node> root = std::make_shared<Node>(4); root->leftPtr = std::make_shared<Node>(2); root->leftPtr->parentPtr = root; root->rightPtr = std::make_shared<Node>(5); root->rightPtr->parentPtr = root; // Outputs to help visualize reference counts std::cout << "root reference count = " << root.use_count() << std::endl; std::cout << "root->leftPtr reference count = " << root->leftPtr.use_count() << std::endl; std::cout << "root->rightPtr reference count = " << root->rightPtr.use_count() << std::endl; std::cout << "root->rightPtr->parentPtr reference count (via lock) = " << root->rightPtr->parentPtr.lock().use_count() << std::endl; std::cout << "root->leftPtr->parentPtr reference count (via lock) = " << root->leftPtr->parentPtr.lock().use_count() << std::endl; return 0; }
Output:
Constructor Constructor Constructor root reference count = 1 root->leftPtr reference count = 1 root->rightPtr reference count = 1 root->rightPtr->parentPtr reference count (via lock) = 2 root->leftPtr->parentPtr reference count (via lock) = 2 Destructor Destructor Destructor
Summary
While shared_ptr
is an excellent tool for memory management, it must be used with caution to avoid pitfalls like cyclic references. weak_ptr
offers a solution to this problem by allowing the existence of references that do not control the lifetime of the resource, thus enabling patterns like parent-child relationships
Very useful//
Solved the biggest problem of memory leak we encounter while practicing trees concept.. Thanks!