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 disadvantage of C++ shared pointer that you might come across while programming. 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 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 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 program. But here is a noteworthy case where the pointer is left as a dangling pointer and beware of it; avoid it in your program.

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 dangling pointer we should assign nullptr to it after the storage ownership is transferred to shared_ptr. This is one solution to prevent any pointer from being left out as 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 forget about what will happen to it afterwards. Since we don’t want to take any risks of the program giving any undefined behaviour due to some dangling pointer the only sure solution is to not intermingle the ownership of a memory between a raw pointer and a shared_ptr. Let raw pointer handle the storage which it points to and let shared_ptr mind his 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

Trying to access the value using *sp will give you an undefined value and on top of that, another dangerous event is about to unfold.

When ‘sp’ cease to exist it will try to free again the storage that had been already freed, 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 outcome then don’t use any raw pointer to assign a dynamic storage to shared_ptr, use direct initialization instead.


iii)Circular reference

shared_ptr utilizes an algorithm known as reference counting to keep track of the number of pointers pointing to the same storage. The storage is freed only when the reference value is reduced 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 represent a situation in which data member points to each other storage and form a circular pattern referencing event and this is known as 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 his nose into the matter of data member 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 inherit 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 will complain. This means we cannot use delete operator in our program and have to run it without calling the delete operator. Since we cannot call delete operator in our program the storage of age data member will go undeleted and this will produce a memory leakage in our program. The same old truth!

Although the case given here is farfetched and also the data member should not be 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 menber as public) should not be encouraged.




Leave a Reply

Your email address will not be published. Required fields are marked *