C++11 Shared pointer smart pointers ; why is it call shared pointers?

In this post we will have a thorough discussion of one of the smart pointers of C++ the C++11 shared pointer smart pointers (or shared_ptr)

The traditional raw pointer suffer from many drawbacks. It doesn’t track of who or how many other pointers are actually pointing to the same object. The most notorious of its behaviour is it doesn’t care what happens to the storage which it points to when something goes wrong.Needless to say,this can lead to many serious problems:undefined behavior , dangling pointer ,etc. to mentioned some. A new kind of pointer was introduced-in C++11 that overcome all the limitations of the raw pointer. These pointers were called smart pointers due to their smarter behaviour and reliability.

Link :C++11 shared_ptr

One thing you must know about C++11 smart pointer is they are template. A smart pointer object is a template object which is made to behave like a pointer. Their main uses in C++ is to handle dynamic memory due to their smarter behaviour. Undoubtedly they can handle the dynamic form of memory allocation better.

There are four types of smart pointers with different properties,one of the pointers will be discussed here:shared pointer written as shared_ptr.


Whta is shared_ptr?

The C++11 shared pointers (or shared_ptr) is a template .And while declaring an object of shared_ptr you must not include the ‘*‘ sign thinking that it is a pointer declaration, it must be declared the way a normal object is declared.To use the shared_ptr you must include the header <memory>.

shared_ptr<int> si ; //int shared pointer 

shared_ptr<string> ss ; //string shared pointer

shared_ptr<int> *sd ; //wrong way of declaration

The third way of declaring the shared pointer object is just plain old wrong.


Shared_ptr Initialization

To initialize the shared_ptr object you must use the direct way of initialization, using the indirect method will give you an error.C++ Learner usually gets confused here over why direct initialization is accepted and indirect method is not accepted. The reason is simple but before we see why? let’s look at the acceptable and unacceptable form of shared_ptr initialization.

shared_ptr<string> sst=new string(“Hello learner”) ;   //error :indirect initialization

shared_ptr<string> sst1(new string(“Hello learner”)) ;   //Accepted:direct initialization
Why only direct initialization is allowed?

The first thing you must know is a constructor of shared_ptr template accepts only an explicit type. This means if we want to assign an object to shared_ptr<string> object the object on the right side must be shared_ptr<string> type and no other type. If the type is different no assignment is allowed.

If we look at the first form of initialization above what happens is, after the memory allocation the new operator will return a string type pointer. But to assign this pointer we require converting the string type pointer to shared_ptr<string> type. As we know the constructor accepts only an explicit type(shared_ptr<string> type) converting the string pointer to shared_ptr<string> type is not possible.So the first initialization method will give an error. However, in the second method, we are simply passing the string pointer as an argument to the shared_ptr<string> constructor(note smart pointer constructor accepts a pointer of the type the class is declared) and so no conversion is required. Hence the object can be initialized.

Link :3 points why shared_ptr and unqiue_ptr constructor are made explicit?

There is another way of initializing a shared_ptr object.In this method we use the function make_shared.Initializing the object using this function is secure as it allocates memory in the heap and returns a pointer of shared_ptr type. To use make_shared function we must declare the type of the object we want to create like the shared_ptr. An example is shown below.

shared_ptr<Data> spd=make_shared<Data>(90.09) ;  ///Data is any class with double as it’s data member

shared_ptr<string> sps=make_shared<string>(“Happy”) ;

make_shared function is pretty handy whenever you want to initialized a shared_ptr.



Accessing the shared_ptr value

To access the value pointed by the shared_ptr the usual normal pointer notation is used. To access the value the ‘*’ sign is added in front of the pointer name.This also holds true for all other smart pointers.

shared_ptr<int> i( new int(908) );

cout<< *i << endl ;

*i=879 ;

cout<< *i << endl ;

Sure deallocation with shared_ptr

The most useful property of smart pointer is sure deallocation whenever the pointer goes out of scope. When raw pointer is used for dynamic allocation we must always call the delete operator to free the storage. Forget to do this and you will suffer a memory leakage. This has been a pain in the ass for dynamic memory users. Say if a function returns a dynamic storage pointer, is there a guarantee that the caller of the function will not forget to delete the storage.Consider the function below.

int* allocate_mem( int i )
{
int *storage= new int(t) ;

return storage ;
}

void func( )
{
int *mem=allocate_mem(56) ;

change_value(mem) ; //if change_value() pass an exception delete mem is never executed

delete mem ;
}

Such function tend to be the source of all our problems. But if we use smart pointer we no longer need to worry about such function. Suppose the pointer return by this function is assigned to a shared pointer then the shared pointer will assumed responsibility of that storage. Now you can be sure that the shared pointer will delete the storage when it goes out of scope or even if an untimely exception is passed by some other function and you can forget about calling the delete operator.

void func( )
{
shared_ptr<int> spt=shared_ptr<int>(allocate_memory(90)) ;

change_value(spt) ;

…   //do anything with spt

}

Now since shared_ptr is used even if change_value() pass an exception the shared_ptr will make sure that the storage is safely deleted. Hence there is no risk of memory leakage here.


Why shared_ptr is called shared pointer?

Another obvious question about shared_ptr is why is it call shared pointer? there are many other smart pointers with different names (note every smart pointer has there own specific characteristic) but why this one is called shared pointer.

link :C++11 shared pointer class internal workings

If we look at the name the first thing that comes to our mind is “sharing “, and it is in fact related to its behaviour. A shared pointer can share the memory which it points to with other shared_ptr objects.When a memory is shared shallow copy is performed not deep copy: meaning the different pointers point to the same storage.If a deep copy is performed then each pointer will point to its own different storage. The concept of shallow and deep copy is shown in the picture below.

C++11 Shared pointer smart pointers shallow copy
Shallow copy

C++11 Shared pointer smart pointers deep copy
Deep copy

Since shallow copy is performed there is a dilemma, here. Consider that pointer1 goes out of scope what will happen to the storage? will it get deleted? no! if it gets deleted the other pointers will not be pointing to any valid memory storage and will be left as dangling pointers.

However,shared_ptr is smart enough to not let that happen so even if one pointer goes out of scope the storage still remains. The storage will get deleted only when the last pointer pointing to the storage goes out of scope.

void func( )
{
shared_ptr<string< st ;

…   //your code

} //st storage gets deleted here

int main( )
{
shared_ptr<int> si(new int(89)) ;
cout<< *si ;
 {
 shared_ptr<int>sii(si) ;
 cout<< *sii << endl ;
 }//sii goes out of scope

cout<< *si << endl ;

func( ) ;

return 0;
} //si storage deleted here

So how does shared_ptr keep track of all the number of pointers that are currently pointing to the same storage.It implement a method known as reference counting.In reference counting there is a data(of unsigned int type) known as reference value associated with each storage and the value of this data is incremented each time a new pointer point to the storage.

Say, if three pointers point to the same storage the reference value related to that storage will have the value 3. Suppose if one of the pointers goes out of scope the reference value is decremented(to 2) and if another pointer goes out of scope the reference value is decremented(to 1) again and so on. The storage gets deleted only when the reference value is reduced to 0: meaning no pointer is pointing to the storage. A more detail explanation of reference counting concept with code example will be shown in another post.

Note:use_count( ) member function of shared_ptr is used to get the reference value of the shared pointer.

shared_ptr<char> sc(new char(‘B’)) ;
  {
  shared_ptr<char>scc(sc) ;
  cout<< use_count() << endl ; //reference value incremented
} //scc goes out of scope so reference value is decremented

cout<< use_count() << endl ;


Casting shared_ptr to another type

Suppose we have a function that accepts shared_ptr, if a raw pointer is passed to this function the shared_ptr will not accept the raw pointer. It seems the compiler cannot perform implicit casting and so the raw pointer cannot be casted to shared_ptr in any way.

void Count( shared_ptr<int>si )
{
… //your code here
}

int main( )
{
count( new int(90) ) ; //error!!

return 0 ;
}

To make the function accept the raw pointer, it must be casted to shared_ptr type first. This can be done either by using static cast or by using the make_shared function.

void Count( shared_ptr<int< si )
{
… //your code here
}

int main( )
{
Count( make_shared<int> (new int(0)) ); //work fine

Count( static_cast<shared_ptr<int>>(new int(9)) ) ; //work fine

return 0 ;
}

Explicit casting from raw pointer to shared_ptr type is permitted. This seems reasonable because by casting the raw pointer to shared_ptr type we are simply creating a new object with the pointer as the data-member. There are no extra activities involved here. In fact, there is no difference between this and the implicit conversion that the compiler execute. Here it’s just that the casting is performed explicitly by us since the constructor accept only explicit type.

There is one more question that remains to be asked,is the reverse castingcasting from shared_ptr to raw pointer– feasible ? .The answer is no! .This shouldn’t be surprising if casting from shared_ptr to raw pointer was possible it wouldn’t make any sense.

Casting from shared_ptr to raw pointer would mean casting an object to a raw pointer, can you imagine what might happen if this occurs? the outcome is bound to be totally senseless and so the compiler won’t permit it. If you need an address of the memory that the shared_ptr point to you can use the member function get(), but note using this function can be more disastrous than productive.


Adding deleter function to shared_ptr.

The shared_ptr delete the memory which it manages by executing a deleting code-which is most probably “delete memPtr”- in its destructor function. This is an efficient method because destructor is called every time a shared_ptr goes out of scope and so the memory is not left out undestroyed.

Instead of employing the shared_ptr original deleting code to delete the storage we can deploy our own function to handle the memory deletion. Such function is known as deleter function and it’s definition doesn’t have to be a complex code but rather it can consist of only one line of code “delete memPtr”.

To add a deleter function in a shared_ptr,it is required that the function name is passed as second argument when the shared_ptr is initialized.The correct syntax of adding a deleter function in shared_ptr is shown below.

void deleterFunction(int *memPtr ) //An instance of a deleter function
{
delete memPtr ;
}

int main( )
{
 {
 shared_ptr<int>sp( new int(89) , deleterFunction ) ; //note the second argument is the deleter function name
 }

return 0 ;
}

Note:the deleter function parameter type should be same as the type for which the shared_ptr class is declared.Since the shared_ptr sp above has it’s own deleter function when it goes out of scope the deleterFunction( ) is called to delete the storage.

There are some disadvantages while using shared pointer they are discused here 4 disadvantage of C++ shared pointer .




Leave a Reply

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