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


The traditional raw pointer suffer from many drawbacks.It is feckless with no sense of authority over what it points to.It doesn’t track of who or how many other pointers is actually point to the same object.The most notorious of it’s behavior is it doesn’t care what happen 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.The dullness of raw pointers is just so terrifying we cannot solely depend on it for all our task.So a new kind of pointer was introduced-in C++11-that can provide a programmer with some basic help which a raw pointer could not.These pointers were called smart pointers due to their smarter behavior and reliability.

One thing you must know about C++11 smart pointer is they are template.So 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 behavior which include keeping track of all the pointers pointing to the same memory storage. So,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.


shared_ptr

C++11 shared_ptr being a template the syntax of it’s declaration should be purely in a template way.Also while declaring an object of shared_ptr you must not include the ‘*‘ sign thinking that it is a pointer declaration,it must be declared in a way a normal object is declared.To use the shared_ptr type you must include the header <memory> because the template of this smart pointer is defined there.

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.


Initialization

To initialize the shared_ptr object you must use a direct way of initialization, using the indirect method will give you an error.C++ Learner usually gets confuse 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 form of initialization which is acceptable and which is not.

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

shared_ptr<string> sst1(new string(“Hello learner”)) ;   ///Accepted:direct initialization

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 happen 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> class constructor(note smart pointer constructor accepts a pointer of the type the class is declared for) and so no conversion is require.Hence the object can be initialized.

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 allocate a memory in the heap and return 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 will be used.So to access the value the ‘*’ sign is added in front of the pointer name.This also holds true for all the 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.Whenever we use a raw pointer 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 user because the question always arises what if we forget? or if an exception is passed before delete operator is called.Say if a function return 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 need no longer worry about such function.Suppose the pointer return by this function is assigned to 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 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.If we look at the name the first thing that comes to our mind is “sharing ” , and it is in fact related to it’s behavior.Shared pointer can share the memory which it points to with other shared_ptr object.When a memory is shared shallow copy is performed not deep copy:meaning the different pointers point to the same storage.If deep copy is performed then each pointer will point to it’s own different storage.The concept of shallow and deep copy is shown in the picture below.


Shallow copy


Deep copy

Since shallow copy is performed for different shared pointers object 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 is currently pointing to the same storage.It implement a method known as reference counting.With 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.

Link :C++11 shared_ptr internal workings

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 type 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 of the class with the pointer as the data-member value.There is no extra activity involve here.In fact,there is no difference with 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 occur? 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.I have made another post dedicated solely on the discussion of the use and drawbacks of this function and the other member functions,you can check out the post here.


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 it’s 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.Now 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 codes but rather it can consist of only one line of code “delete memPtr”;deleting the memory.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 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.


 


Some dangerous outcome while using shared_ptr object

shared_ptr is smart.But it won’t be an understatement if I say shared_ptr is not smart enough to prevent all the doom that is waiting to happen when used in an unsmart way.Of course everything has drawbacks and shared_ptr is no exception.The only way to prevent the misuse of shared pointer rest entirely on the programmer.He must be always aware to keep his program in check and not cross certain boundary which is liable to give some undefined result.Here we will look at some of these boundaries that must not be crossed while using the shared_ptr object.

 

i)Don’t mingle raw pointer and shared_ptr while handling a dynamic storage.

Shared pointer is so fidel to the storage which it points to that it never forgets to delete it 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 dangling 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 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 transfered 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 such as sentence after passing it to the function as a shared_ptr object argument.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 our program giving undefined behavior 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 point to and let shared_ptr object mind his own business.


 

ii)Assigning raw pointer to two different shared_ptr.

When two shared_ptr assume 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 invalid memory location.Consider the code below.

int *i=new int(90) ;

shared_ptr<int>sp=static_cast<shared_ptr<int> >( i ) ;
{
shared_ptr<int>sp1=static_cast<shared_ptr<int> >( i ) ;
}

cout<< *sp ; ///undefined

Trying to access the value using *sp will give you an undefined value and here on top of that another dangerous event is about to be 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 a 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 happen if utilizing all your programming skill you wrote a program where the reference count just won’t reduced to zero no matter what. Believe me there can be such case and it is known as 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 point to each other storage and form a circular pattern referencing event so it is known as circular reference.This problem can be solved using another smart pointer known as weak_ptr .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 dangerous outcome when shared_ptr object does not mind his own bussiness 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 you can have a bad feeling about this already.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=”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 went out of scope.So what happen 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 should not be encouraged.


Related Link

->C++11 Member functions of shared_ptr class: use_count() – unique() – get() – swap( ) – reset( ) .

->C++11 shared_ptr class internal workings with code example.

C++11 unique_ptr smart pointer

C++11 weak_ptr a weaker shared_ptr

C++11 bad_weak_ptr :a class thrown as exception by shared_ptr

C++11 enable_shared_from_this uses