C++11 weak_ptr :a weaker shared_ptr


C++11 weak_ptr :a weaker shared pointer

The smart pointer:shared_ptr,is more dynamic and secure than the traditional raw pointer but it seems to suffer from some drawbacks which cannot be resolved by any using shared_ptr itself(please visit this link to read about the drawbacks of shared_ptr ).These limitations faced by the shared_pt r is the main culprit behind the introduction of weak_ptr in C++11.Although using a shared_ptr may not be as dangerous as we might imagined but not providing a resolution for the drawbacks will leave a big hole in the features of shared_ptr which one might consider intimidating.One can say the existence of weak_ptr makes shared_ptr more secure by keeping the shared_ptr at bay and not using it directly.

In this post we will see some ways to implement weak_ptr and also see how weak_ptr can solve the cyclic reference problem.

weak_ptr

The most important thing to note about weak_ptr is it cannot own a resource of it’s own.This means like the other smart pointer we cannot allocated a new memory and make a weak_ptr manage that memory.It can only point to the resource of another smart pointer which is a shared_ptr.To sum it up weak_ptr can only point to the resource of shared_ptr.If there is no shared_ptr resource weak_ptr has no purpose,it will be like a ronin with no master to serve;doodling purposelessly.And since it does not operate the resource directly the term ‘weak’ is coined for it.

To make a weak_ptr point to the shared_ptr resource the shared_ptr should be passed as an argument during weak_ptr declaration or you can use the ‘=’ operator to assign the shared_ptr to weak_ptr.Initializing weak_ptr
using both of these methods are shown below.

shared_ptr<string>sp(new string(“I like Xbox”) );

weak_ptr<string>wp(sp) , //works fine
  wp1 ;

wp1=sp ; //works fine

 





Accessing the value pointed by the weak_ptr

To access the value the weak_ptr manages we cannot use the ‘*’ operator like the other smart pointers.weak_ptr cannot access the value directly.To access the value we have to create another instance of the shared_ptr which the weak_ptr is managing the mempry for and using this shared_ptr we can access the value.To create a shared_ptr from the weak_ptr the member function lock() is use.This functions returns a shared_ptr to the object to which the weak_ptr points to.Consider the program below.

shared_ptr<string>sp(new string(“hard drvie”) ) ;

weak_ptr<string>wp(sp) ;

auto spNew=wp.lock( ) ;

cout<< *spNew ;

Using the lock() function can get nasty if not careful.Suppose the resource which the weak_ptr points to is already destroyed then the lock() function will be returning a shared_ptr that points to some invalid storage and accessing it will give undefined result.So whenever lock() is used we must make sure to validate the weak_ptr first.This can be done by using the normal if() statement,consider the code below.

weak_ptr<int>wp(sp) ;

{
shared_ptr<int>sp(new int(890) );
wp=sp ;
}

auto spNew=wp.lock( ) ; ///not recommended
cout<< *spNew ; ///undefined

if( auto spValid=wp.lock() ) //check for validity,recommended
{
  cout<< *spValid ;
}
else
  cout<< “Storage has expired” ;

If the storage is valid the value of spValid is printed out else “Storage has expired” is printed out.


 


Cyclic reference problem solved by weak_ptr

If you have read the shared_ptr pointer post given in this tutorial I have pointed out some limitations the shared_ptr might render when using it in your program.In this section we will see how to solve one of the limitations of shared_ptr namely the cyclic reference problem using weak_ptr.

The cyclic reference is a case when the shared_ptr will never achieved it’s reference count as 0 and so the memory gets leak in your program.An instance of cyclic reference case is shown below.

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=b ; ///spB points to b
b->spA= a ; ///spA points to a

return 0 ;
}

The reference count of the shared_ptr a and b will never reduce to 0 so the destructor is never called leading to memory leakage.But if we use a weak_ptr instead of shared_ptr as the data member inside both the classes we can easily diverge from the cylic reference issue.

class A
{
public:
weak_ptr<B>spB ;

A() { }

~A( ) { cout<<“A destructor \n”; }
};

class B
{
public:
weak_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=b ; ///spB points to b
b->spA= a ; ///spA points to a

return 0 ;
}

Using weak_ptr does not increase the reference count of the shared_ptr a and b.So the reference count of a and b is 1,but if shared_ptr is used the reference count will increment to 2.In case of weak_ptr when the object ‘a’ goes out of scope the reference count is decrease to 0 and so the object’s destructor is called and frees the storage ,ditto happens for the object ‘b’.But if shared_ptr is used,when object a goes out of scope the reference count will reduce to 1 and so the destructor cannot be called leading to memory leakage,the same goes for the object ‘b’.



Member functions of weak_ptr

Some of the member functions of weak_ptr are the same as the shared_ptr member functions.The functions name similar to the shared_ptr functions also perform the same operations.This is expected because weak_ptr purpose is to manage the memory of shared_ptr -although it has no direct ownership over the storage- and see that code using shared_ptr does not render any unexpected output.And weak_ptr does so through it’s member functions.Here we will see all the member functions of weak_ptr which are similar to shared_ptr and which are not.

i)use_count()

use_count() gives the number of shared_ptr currently pointing to the storage which the weak_ptr points to.

shared_ptr<char<sp(new char(‘B’)) , sp1(sp) ;
weak_ptr<char> wp(sp) ;

cout<< wp.use_count() ;

The output is 2.

ii)reset()

The purpose of reset() function is to relinquish weak_ptr from the memory it manages.So if a reset() function is called the weak_ptr will no longer point to any storage.

shared_ptr<int> sp(new int(789) );

weak_ptr<int> wp(sp);

wp.reset() ;

cout<< wp.use_count() ;

The output is 0

iii)expired()

This function will return true if the use_count() is 0.So the underlying meaning of expired() is it returns true only when the weak_ptr points to some storage and when it does not-when it is a doodler- it will return false.

shared_ptr<string>sp(new string(“Shoes and games”);
weak_ptr<string>wp(sp) , wp1 ;

cout< wp.expired() << endl //gives true
<< wp1.expired() ; //gives false

iv)lock()

If the weak_ptr expired() function returns true then lock( ) will return NULL but if the expired() function returns false then lock( ) will return a shared_ptr to the object to which the weak_ptr points to.

weak_ptr<string>wp(sp) ;

if( auto sp1=wp.lock() )
{
cout<< sp1.use_count() ;
}

v)swap()

The swap() function is a non-member function of weak_ptr.It’s function is to simply swap the memory of the two weak_ptr passed as arguments.

shared_ptr<string>sSp(new string(“Games n fun”)) , sSp1(sSp) ;
weak_ptr<string> sWp(sSp) ,wp(sp) ;

cout<< sWp.use_count() << endl //gives 2
<< wp.use_count() ; //gives 1

vi)owner_before( )

This function will accept either a weak_ptr or a shared_ptr as it’s argument and the return type is bool.As the name states it checks if the weak_ptr points to the storage of the argument passed formerly.The function returns true if the weak_ptr point to the storage formerly else it returns false.

shared_ptr<int>sp4(new int(90)) ;
weak_ptr<int>wp2(sp4) , wp3=wp2 ;

cout<< wp2.owner_before(wp3) ;
 << wp3.owner_before(wp2) ;

wp3.reset( ) ;

cout << wp3.owner_before(sp4) << endl
  << wp3.owner_before(wp2) << endl ;

The output is

0
0
1
1

After reset() is called on wp3 it does not point to any storage but note that it is a former pointer to the storage of sp4 and wp2.So the output of the owner_before() is true after reset() is called.


Related Link

C++11 shared_ptr

->shared_ptr class internal workings with code example.

->bad_weak_ptr :a class thrown as exception by shared_ptr.