Main advantage of shared_ptr is that it automatically releases the associated memory when not used any more.
But if we don’t use shared_ptr carefully then this advantage can turn into a disadvantage. Let’s look how,
Suppose I have to design a binary tree and in it the node contains a pointer to left and right children.
Frequently Asked:
#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<<"Contructor"<<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->rightPtr = std::make_shared<Node>(5); Â Â Â return 0; }
In above example, everything goes fine i.e.
3 times constructor will called and then 3 times destructor,
it means complete memory is deleted.
But if we add an another small requirement i.e. each node will contain a pointer to parent’s node. Then it will cause problem with shared_ptr.
[showads ad=inside_post]
Checkout modified example,
Best Resources to Learn C++:
#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<<"Contructor"<<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; }
Now constructor will be called 3 times but there will be no call to destructor and that is a memory leak.
Reason of this problem with shared_ptr is cyclic references i.e.
If two objects refer to each other using shared_ptrs, then no one will delete the internal memory when they goes out of scope.
It happens because shared_ptr in its destructor after decrementing the reference count of associated memory checks if count is 0 then it deletes that memory and if it’s greater than 1 then it means that any other shared_ptr is using this memory.
But in this kind of scenario these shared_ptrs will always found count greater than 0 in destructor.
Let’s reconfirm this for above example,
When ptr’s destructor is called,
o   It decrements the reference count by 1.
o   Then checks if current count is 0 but that is 2 because both left and right child has a shared_ptr object referencing to parent i.e. ptr.
o   Left and Right Child will be deleted only when memory for ptr will be deleted but that’s not going to happen because reference count is greater than 0.
o   Hence memory for neither ptr nor its children will be deleted. Therefore no destructor was called.
Now How to fix this problem?
Answer is using weak_ptr.
Weak_ptr allows sharing but not owning an object. It’s object is created by a shared_ptr.
          std::shared_ptr<int> ptr = std::make_shared<int>(4);    std::weak_ptr<int> weakPtr(ptr); weak_ptr<int>
With weak_ptr object we cannot directly use operators * and -> to access the associated memory. First we have to create a shared_ptr through weak_ptr object by calling its lock() function, Â then only we can use it.
Check below example,
#include <iostream> #include <memory> int main() { Â Â Â 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;Â Â Â Â Â Â return 0; }
Important Point: lock() can return empty shared_ptr if that shared_ptr us already deleted.
Improving our Binary tree example with weak_ptr
In case of cyclic references i.e. two objects refer to each other using shared_ptrs, change one object to contain weak_ptr instead of shared_ptr.
#include <iostream> #include <memory> class Node {    int value;    public:    std::shared_ptr<Node> leftPtr;    std::shared_ptr<Node> rightPtr;    // Just Changed the shared_ptr to weak_ptr    std::weak_ptr<Node> parentPtr;    Node(int val) : value(val)    {       std::cout<<"Contructor"<<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;    std::cout<<"ptr->rightPtr->parentPtr reference count = "<<ptr->rightPtr->parentPtr.lock().use_count()<<std::endl;    std::cout<<"ptr->leftPtr->parentPtr reference count = "<<ptr->leftPtr->parentPtr.lock().use_count()<<std::endl;    return 0; }
Hence by using weak_ptr the memory leak problem is solved for binary tree nodes.
Very useful//
Solved the biggest problem of memory leak we encounter while practicing trees concept.. Thanks!