4 disadvantage of C++ shared pointer smart pointers
The C++ shared pointer(or shared_ptr) has some disadvantages despite being a smart pointer. Here we will look at 4 disadvantages of C++ shared pointer that you might encounter while programming in C++. The cases discussed here are rather some specific situations and need not necessarily treat them as limitations of shared_ptr.
Link :C++11 Shared pointer smart pointers
i)Do not intermingle raw pointer and shared_ptr while handling a dynamic storage.
Shared pointer is very fidel to the storage it points to. In fact, it never forgets to delete the storage when it goes out of scope. This is indeed helpful but it becomes dangerous when the storage has an original owner as a raw pointer and the shared_ptr object is merely a second owner of the storage.
Consider the case, if the shared_ptr goes out of scope the storage get deleted, but what about the raw pointer, it will be left as a dangling pointer not pointing to any valid storage.
int main( ) { int *mem=new int(786) ; { shared_ptr<int> sec=make_shared(mem) ; } //storage deleted //mem left as a dangling pointer return 0; }
The mem pointer is left as a dangling pointer. The case shown above is rather a rare case that may or may not happen in your programs. But here is a noteworthy case where the pointer is left as a dangling pointer. Beware of it and avoid it in at all cost your programs.
void func( shared_ptr<string>str1 ) { … //code here }//storage of str1 deleted here int main( ) { string *sentence=new string(“Happy are those who program in C++.”) ; func( make_shared<string> (value) ); //sentence left as a dangling pointer return 0 ; }
To prevent the sentence pointer from being left out as a dangling pointer, we should assign nullptr to it after the storage ownership is transferred to the shared_ptr. This is one solution to prevent any pointer from being left out as a dangling pointer. But we often tend to neglect the status of the pointer after passing it to the function as a shared_ptr .
After passing to shared_ptr we feel secure that the storage pointed by it will be deleted for sure and forgets what happened to it afterwards. Since we don’t want to take any risks in our program giving any undefined behaviour due to some dangling pointers, the only sure solution is to not intermingle the ownership of memory between a raw pointer and a shared_ptr. Let raw pointer handle the storage which it points to and let shared_ptr mind its own business.
ii)Assigning raw pointer to two different shared_ptr.
When two shared_ptr assume the responsibility of the same storage through the same raw pointer you can be sure that your program is about to behave in an unexpected way. Since in this case, the reference count is 1 for each shared_ptr, when one shared_ptr goes out of scope the other shared_ptr pointer is left pointing to an invalid memory location.Consider the code below.
int *i=new int(90) ; shared_ptr<int>sp=static_cast<shared_ptr<int> >( i ) ; //sp points to i { shared_ptr<int>sp1=static_cast<shared_ptr<int> >( i ) ; //sp1 points to i } cout<< *sp ; //undefined
The pointer sp1 exits only inside that particular region bounded by the braces. Outside the region, it has ceased to exist and also it has deleted the storage which it point to when it ran out of scope. So outside the region trying to access the value through ‘*sp’ returns an undefined value and on top of that, another dangerous event is about to unfold.
When ‘sp’ ceases to exist it will try to free again the storage that has been already deleted by sp1, now there is a high chance that your program will behave irrationally and at worst case scenario, it will crash. Well if you want to prevent such an outcome then don’t use any raw pointer to assign dynamic storage to shared_ptr, use direct initialization instead.
iii)Circular reference
shared_ptr utilizes an algorithm known as reference counting to keep tracks of the number of pointers pointing to the same storage. The storage is freed only when the reference value reduces to 0. Now then what happens if in utilizing all your programming skills you wrote a program where the reference count just won’t reduce to zero no matter what. Believe me there can be such a situation and it is known as the circular reference.Consider the program below.
class B ; class A { public: shared_ptr<B>spB ; A() { } ~A( ) { cout<<“A destructor \n”; } }; class B { public: shared_ptr<A>spA ; B() { } ~B( ) { cout<< “B destructor \n”; } }; int main( ) { shared_ptr<A>a(new A() ) ; shared_ptr<B>b(new B() ) ; a->spB=static_cast<shared_ptr<B>>( b ) ; //spB points to b b->spA=static_cast<shared_ptr<A>>( a ) ; //spA points to a return 0 ; }
Now run the program without debugging it, and press ENTER you will see destructor is never called.
Suppose ‘a’ goes out of scope the destructor cannot be called yet because one other shared_ptr spB of B class is pointing to the same storage.
Again when ‘b’ goes out of scope the destructor won’t be called as the shared_ptr ‘spB’ of A still owns the storage pointed by ‘b’ and so the reference count is still not zero.
This presents a situation in which data member points to each other storage and form a circular pattern referencing event and this is known as a circular reference. This problem can be solved using another smart pointer known as weak_ptr. The weak_ptr will be discussed in another post.
iv)Involving shared_ptr with a class data member whose object is dynamically allocated
This is another case that can lead to a dangerous outcome when shared_ptr object does not mind his own business and poke its nose in the matter of data members of another class.
When an object is dynamically allocated, the pointer which is assigned the address of the storage has the solemn right to take care of that storage deletion by calling the delete operator. Supposing a shared_ptr object goes and inherits the right to the storage of one of the data members. Consider the program below.
class Database { public: string name ; int age ; Database( string nam=” “, int ag=0 ):name(nam),age(ag) { } ~Database( ) { } }; int main( ) { Database *dat=new Database(“Happy” , 5 ) ; { shared_ptr<string>new_name=shared_ptr<string>( &(dat->name) ); //new_name points to dat storage new_name=”Sad” ; } delete dat ; //error! return 0 ; }
When the delete operator tries to delete the storage the compiler will complain. When the delete operator is called it is meant to delete the whole storage of the memory pointed by dat. But a part of the storage namely the storage of the data member name is already deleted when the shared_ptr new_name goes out of scope.
So what happens is when the delete operator tries to free the storage, it does so for the storage which is already freed and to this, the compiler complains. This means we cannot use delete operator in our program and have to run it without calling the delete operator. Since we cannot call the delete operator in our program the storage of age data member will go undeleted and this will produce a memory leakage. The same old truth!
Although the case given here is farfetched and also the data members should not be made public at all cost. But I still thought it is a case worth looking into because we now know why such programming style(putting data members as public) should not be encouraged.