So far, we have discussed the positive side of shared_ptr with examples. However, shared_ptr fails to clean up the memory when there is a circular dependency in the application design. Either the application design must be refactored to avoid cyclic dependency, or we can make use of weak_ptr to resolve the cyclic dependency issue.
Consider there are three classes: A, B, and C. Class A and B have an instance of C, while C has an instance of A and B. There is a design issue here. A depends on C and C depends on A too. Similarly, B depends on C and C depends on B as well.
Consider the following code:
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
using namespace std;
class C;
class A {
private:
shared_ptr<C> ptr;
public:
A() {
cout << "nA constructor" << endl;
}
~A() {
cout << "nA destructor" << endl;
}
void setObject ( shared_ptr<C> ptr ) {
this->ptr = ptr;
}
};
class B {
private:
shared_ptr<C> ptr;
public:
B() {
cout << "nB constructor" << endl;
}
~B() {
cout << "nB destructor" << endl;
}
void setObject ( shared_ptr<C> ptr ) {
this->ptr = ptr;
}
};
class C {
private:
shared_ptr<A> ptr1;
shared_ptr<B> ptr2;
public:
C(shared_ptr<A> ptr1, shared_ptr<B> ptr2) {
cout << "nC constructor" << endl;
this->ptr1 = ptr1;
this->ptr2 = ptr2;
}
~C() {
cout << "nC destructor" << endl;
}
};
int main ( ) {
shared_ptr<A> a( new A() );
shared_ptr<B> b( new B() );
shared_ptr<C> c( new C( a, b ) );
a->setObject ( shared_ptr<C>( c ) );
b->setObject ( shared_ptr<C>( c ) );
return 0;
}
The output of the preceding program is as follows:
g++ problem.cpp -std=c++17
./a.out
A constructor
B constructor
C constructor
In the preceding output, you can observe that even though we used shared_ptr, the memory utilized by objects A, B, and C were never deallocated. This is because we didn't see the destructor of the respective classes being invoked. The reason for this is that shared_ptr internally makes use of the reference counting algorithm to decide whether the shared object has to be destructed. However, it fails here because object A can't be deleted unless object C is deleted. Object C can't be deleted unless object A is deleted. Also, object C can't be deleted unless objects A and B are deleted. Similarly, object A can't be deleted unless object C is deleted and object B can't be deleted unless object C is deleted.
The bottom line is that this is a circular dependency design issue. In order to fix this issue, starting from C++11, C++ introduced weak_ptr. The weak_ptr smart pointer is not a strong reference. Hence, the object referred to could be deleted at any point of time, unlike shared_ptr.