C++11 unique_ptr : what makes it unique? (includes C++14 features)


C++11 unique_ptr

The smart pointer:shared_ptr ( if you don’t know what is shared_ptr you can visit this link ) has many advantages over the traditional raw pointer.It can keep track of the pointers status -alive or deceased- and free the storage when all the pointers goes out of scope.These smart behavior of shared_ptr makes it stand out from the raw pointers.

The unique_ptr that we are about to discuss here does exhibit some of the properties of shared_ptr but not all.shared_ptr allow sharing of resources .And so naturally many shared_ptr can share one same resources but without creating any conflict with one another.The unique_ptr on the other hand tend to be selfish and so sharing is not allowed.If a unique_ptr owns a memory it becomes the sole owner until it goes out of scope.Since each unique_ptr owns a unique storage hence,the name unique pointer.The pictorial representation below shows the behavior of shared_ptr and unique_ptr.

C++11 shared_ptr and unique_ptr
Fig. shared_ptr and unique_ptr

By the looks of it I think it would suit more if unique_ptr was named selfish_ptr.Well the creator didn’t because it has other properties which makes it worthy of the name unique.And it won’t be an exaggeration if I say those other properties out weight the absence of sharing tendency that unique_ptr exhibit and of course I am going to show you here how? .But before we discuss the unique properties,let us see first how to declare and initialized a unique_ptr.


Declaring and initializing unique_ptr

unique_ptr like the shared_ptr is a template and it’s constructor accept only an explicit type.That means only direct initialization is allowed.Like the shared_ptr function make_shared the new C++14 has introduced make_unique function allowing the creation of a new storage and initializing it to unique_ptr.

The function make_unique will accept a value-as an argument-of the type.And what this function does is, it create a storage that can hold the value provided as the argument and return a unique_ptr that points to the storage .The returned unique_ptr is assigned to the our unique_ptr.An example is provided below.

unique_ptr<int> up(new int(90)) ;
unique_ptr<int> upp=make_unique<int>(4352) ; ///work fine

unique_ptr<int>up1 ;

cout<< *up << endl ///prints 90
<< *upp << endl ; ///prints 4352

cout<< *up1 << endl; ///undefine

The unique_ptr up1 is not initialized to any memory,it is a doodler pointer with no ownership of any storage.For such pointer the storage can be assigned later using a member function reset() or release() (discussed later).You may think we can also use a copy constructor or assignment operator to assign a storage to unique_ptr.No! we can’t the compiler won’t allow it.

unique_ptr<int>up(new int(90)) ;

unique_ptr<int>up1(up) ; ///error,copy constructor call

///or say
up1=up ; ///error,assignment operator call

The reason why the above assignment method is not allowed is explained in another post,the link is provided below.

Link :3 points why shared_ptr and unqiue_ptr constructor is made explicit





why are copy constructor and operator=( ) disable?

The reason for this tend to bend towards the behavior the unique_ptr exhibit.The unique_ptr purpose is to have the solemn rights over the storage which it points to.Copy constructor and operator=() purpose on the other hand is to share the storage or make sharing of the storage or object with another object possible.If you compare their purpose they are like fire and water-if one exist the other must perish- not intermingable at all times.To know how these two functions are disable (if you are using code::blocks) go to the directory “C:\CodeBlocks\MinGW\lib\gcc\mingw32\4.7.1\include\c++\bits\unique_ptr.h” and go to line 256 and 2557 ,you will see something like this

unique_ptr(const unique_ptr&) = delete ;
unique_ptr& operator=(const unique_ptr&) = delete ;

The =delete at the end of the function name means the compiler will not generate those functions for you.In another sense,it means they are disable so they cannot be called.

If you want to transfer the ownership of the storage from one unique_ptr to another you can use reset( ) and release( ) member function.unique_ptr reset( ) member function does exactly the same as the reset() function of shared_ptr.If you are familiar with the shared_ptr reset( ) function then you already know how unique_ptr reset( ) function.If you are not familiar with it worry not these two member functions and some other member functions are discussed later.


Passing a unique_ptr as argument.

Whenever you want to pass a unique_ptr to another function what are the ways that compiler allows you to do so?.The compiler allows you to pass a normal object as a value or as a reference or as a pointer.But when you are passing a unique_ptr you cannot pass it by value.The reason is simple when you pass an argument by value a copy constructor is called to copy the argument but we know unique_ptr doesn’t allow copy constructor call as it is against the rule.Hence the compiler won’t allow it.

void func( unique_ptr<int> upArg )
{
cout<< *upArg << endl ;
}

int main( )
{
unique_ptr<int>up(new int(90) ) ;

func(up) ;  ///error

return 0 ;
}

To pass a unique_ptr as an argument you can either pass it as a reference or as a pointer.In both the cases there is no extra need for call to copy constructor as we are simply passing the address of the unique_ptr.If the argument is a unique_ptr pointer then in the function to access the value a double * sign must be used.Consider the program below.

void funcRefArg( unique_ptr<int> &Arg ) //pass by reference
{
cout<< *Arg ;
}

void funcPtrArg(unique_ptr<int> *Arg )//Arg is a pointer to unique_ptr
{
cout<< *(*Arg ) ; ///Beware
}

int main( )
{
unique_ptr<int>up(new int(90) );

funcRefArg(up) ;

funcPtrArg(*up) ;

retrun 0 ;
}

unique_ptr are not a pointer but an object made to behave like a pointer so when it is passed as pointer we are passing the address of the object not the pointer inside the unique_ptr class(that points directly to the storage).As a result we use double ‘*’ sign ,the inside ‘*’ sign used is the normal syntax form that accompany while accessing the unique_ptr value and the outside ‘*’ sign is due to argument being passed as actual pointer.


Returning unique_ptr from a function

Returning a unique_ptr from a function is allowed.When a function return a unique_ptr the value is copied to the variable in the receiving side.This is contradictory to the fact that any copying or sharing of resources is not allowed with unique_ptr.However in this case an exception is applied.Consider the code below.

unique_ptr<int> funcRetArg( )
{
unique_ptr<int>up(new int(8)) ;

return up ;
}

int main( )
{
int i=*( funcRetArg( ) ) ;
cout<< *( funcRetArg( ) ) ;

return 0 ;
}

The unique_ptr value returned but the funcRetArg( ) is copied to the ‘i’ variable,but why does unique_ptr allow copying of data here? when the function funcRetArg( ) execution ends the memory occupied by ‘up’ is a temporary storage ,it will get destroy as soon as the value of ‘up’ is assigned to ‘i’.Since the storage is temporary and has no further uses the unique_ptr will allow copying of the data.We give another term for such behavior of unique_ptr -allowing copying of temporary object- as moving of object.How does unique_ptr perform moving of object and the underlying principle of this method is discuss in another post.For now knowing that unique_ptr allow copying of temporary object through moving of object is good enough.


Array and unique pointer

Suppose you want to make a unique_ptr manage an array resource then you cannot use the normal unique_ptr syntax declaration and allocate an array storage and make the unique_ptr point to the array storage.With this syntax you are not actually making the unique_ptr handle the array and things can go wrong.Consider the code below.

unique_ptr<int>up(new int[2]{4,5} );

cout<< *up ; ///access the first element

 
The above method holds two disadvantages :

i)You cannot access the second element,so up[1] is undefined .

ii)The pointer ‘up’ will only take responsible for the first element.This means when the pointer ‘up’ goes out of scope it will only free the storage of the first element and so the second element storage is leaked.And we do not want this.

To solve this problem C++11 introduce another way of managing an array.In this method we have to make the template know that we are going to make the unique_ptr manage an array type resources.To do this we will add the ‘[ ]‘ after the data type when we passed the template argument type during the unique_ptr declaration.The code below shows an instance of this method.

unique_ptr< int[] > upArr(new int[3]{3,5,6} ); ///note the ‘[]’ after the type

cout<< apArr[0] << endl
<< apArr[1] << endl
<< apArr[2] << endl ;

Now we have no problem accessing all the elements of the array and also all the storage is deleted safely when the pointer upArr goes out of scope.

The drawback of adding the subscript ‘[]’ in the template argument declaration is the ‘*’ operator becomes non-functional.Accessing the array with ‘*’ operator is just plain error.

cout<< *apArr ; ///error


 



Adding a deleter function to unique_ptr.

In shared_ptr we can add a deleter function when the shared_ptr is initialized by passing it’s name as a second argument.Also in the reset( ) shared_ptr member function ,if two arguments are passed the second argument is a function name.This function purpose-the second argument function- is to delete the storage the shared_ptr points to and so it is called when the shared_ptr goes out of scope.Such function is known as deleter function because it’s purpose is to only delete the storage.An example of implementing deleter function in shared_Ptr is shown below.

///Deleter function
void delete_func(int *i )
{
delete i ;
}

int main( )
{
 {
 shared_ptr<int>sp(new int(89) ) ;

 sp.reset(new int(234) , delete_func ) ;

 } ///delete_func( ) called here

return 0 ;
}

In unique_ptr we can also add a deleter function.In unique_ptr however we cannot add a deleter function when the reset() member function is called but we can when the unique_ptr is initialized.During initialization the deleter function name is passed as second argument and it is also required that the pointer to the deleter function type is mentioned as a second type in the template parameter during the unique_ptr template declaration.Can’t understand what I am saying? look at the code below.

void deleterFunction(int *memPtr )
{
delete memPtr ;
}

unique_ptr<int , deleterFunction_Type>up(new int(89) , deleterFunction ) ;

The second argument type of the unique_ptr deleterFunction_Type is a pointer to the deleterFunction type.To obtain a function pointer type we can use either a typedef or decltype( ) function (if you don’t know what is decltype() visit this link) .The code below shows how to achieved it.

void deleterFunction( int *memPtr )
{
delete i ;
}

typedef void ( *deleterFunction_Type )( int *memPtr ) ; /*deleterFunction_Type is a pointer to deleterFunction type */

int main( )
{
 {
 unique_ptr<int , deleterFunction_Type>up(new int(89) , deleterFunction ) ; ///works fine

//or you can also use decltype( )

 unique_ptr<int , decltype( deleterFunction )* >up(new int(89) , deleterFunction ) ; ///works fine

 } ///deleterFunction( ) is called here

return 0 ;
}

If decltype( ) is used note the ‘*‘ sign appended after the closing bracket.The function decltype( ) only returns the type of the deleterFunction but we want a pointer to that function type as a second argument type so the ‘*’ sign is attached.



Some member functions of unique_ptr

unique_ptr template class also has member functions which allow the unique_ptr object to accomplish certain task.Some of it’s member functions are similar to shared_ptr while some other are specific to the unique_ptr class.Since unique_ptr does not use reference counting method as it does not share it’s resources to other pointer so, the member function of shared_ptr that provides information related to reference counting such as use_count( ) , unique( ),etc. is not included in the unique_ptr class.

The member function common to both the shared_ptr and unique_ptr are:

i)get( ) : This function return the raw addrss of the memory pointed by the
unique_ptr.

ii)swap( ) : This function swap the memory of the two unique_ptr.

Please visit the link Member functions of shared_ptr class: use_count() – unique() – get() – swap( ) – reset( ) to have a detail view of the member function common to the shared_ptr class.

The member functions found specifically in unique_ptr are:

i)release( ).

ii)reset( ):This function is found in shared_ptr class but it does not accept two arguments.

Both of these functions are discuss in detail below.

i)release( ) member function

The purpose of release( ) function is to relinquish the ownership of the memory which the unique_ptr points to.This means if release( ) function is invoked the unique_ptr will no longer have the sole rights over the storage which it initially points to.After the unique_ptr losses the ownership over the storage an address of that storage is returned.Consider the code below.

unique_ptr<int*gt;up(new int(78) );

auto *newOwner=up.release( ) ; /*newOwner now owns the storage which holds the value 78 */

cout<< *up ; ///undefined and dangerous

cout<< *newOwner ;

delete newOwner; ///Do not forget

If release( ) function is called without assigning the returned address to any raw pointer or any other pointer then there is bound to be leakage of memory.

unique_ptr<int>up(new int(09)) ;

up.release( ) ; ///memory lekage here

So whenever you call release() function do not forget to assign the return address to some other pointer that will handle the storage safely.

Since release() return a raw address there is another way of using it.When a new unique_ptr is created you can initialized this new unique_ptr to any existing unique_ptr using the release( ) function.In this case the newly created unique_ptr will point to the storage of the unique_ptr of which the release( ) function was called.A code example is given below.

unique_ptr<string>upS(new string(“Wow!”) ) ;

unique_ptr<string>upS1 ( upS.release() ) ;

cout<< *upS ; ///undefined and dangerous
cout<< *upS1 ; ok

upS1 now points to the storage containing the string “Wow!”.

ii)reset( ) member function

The purpose of reset( ) function vary depending on whether an argument is passed to it or not.You can call a reset() function either with no argument or by passing one argument.When a reset() function is called without passing any argument it’s purpose is to free the memory pointed by the unique_ptr .

unique_ptr<double> up(new double(90.346) );

up.reset( ) ; ///frees the memory

cout << *up ;///undefined and dangerous

After freeing the memory up will point to nullptr.

If an argument is passed to reset( ) function,then the argument must be an address of a dynamic storage.In this case what happen is that the previous storage is deleted and the unique_ptr will point to the new storage whose address is passed as the argument.

unique_ptr<char>upC(new char(‘B’) ) ;

upC.reset(new char(‘V’) ) ;

cout<< *upC ;

upC now points to the new storage with the character ‘V’.The argument required in reset() function is simply an address of the dynamic storage so we may also use the return value of the release( ) function-which is an address of a dynamic storage- as the reset( ) function argument and call the function.Consider the code below.

unique_ptr<int>up(new int(89) ) , up1 ;

up1.reset( up.release( ) ) ;

cout<< *up1 ;

Whenever a release( ) function is call instead of assigning newly the storage to a built-in pointer,you can use the reset( ) function to assign the address to another unique_ptr like we did above.This makes sure that the storage is in secure hand,as who knows assigning the storage to a raw pointer might just be another superflous risk that you may be undertaking if something goes wrong;resulting to leakage of memory.

In the beginning of the post I have said unique_ptr have a sole rights over the storage which it points to.And it does not even share the ownership of the storage with any other pointer.This properties of unique_ptr makes it unique and different from the shared_ptr.But then again if a reset( ) function is called upon a unique_ptr it loses it’s ownership rights and at times it is assigned the ownership of a new storage.So if reset() is used there is an exception in the behavior of the unique_ptr it exhibit-like a hard-hearted person who does not care about other people.In other words,reset( ) function provides some flexibility in the the behavior of the unique_ptr.





Casting of unique_ptr

We can cast a raw pointer to unique_ptr using static_cast function.Note make_unique cannot convert a raw pointer to unique_ptr it can only create a storage for unique_ptr to point to.

int *mem=new int( 1234 ) ;

unique_ptr<int>up=static_cast<unique_ptr<int> >( iNew ) ;

cout<< *up ;

Casting is allowed,that doesn’t mean you should cast the raw pointer whenever you feel like it,it is insecure and error prone due to the reason discuss later in the topic “Limitations of unique pointer“.

There is no way to cast a unique_ptr to raw pointer type or another smart pointer.Even if casting from unique_ptr to raw pointer is allowed it wouldn’t make any sense because raw pointer is just a built-in pointer and unique_ptr is a class template.Also conversion between the smart pointers is not allowed,if it is we can already foresee many insidious errors it may introduce in our program.


2 points why unique_ptr should be preferred.

We have seen how unique_ptr behave and how some of it’s member functions can be used to carry out various operation.Here we will see three cases where the use of unique_ptr will make the program more secure and faster.

First::Sure deallocation with unique_ptr.

If you have read the post on shared_ptr of this tutorial,this exact same topic is discussed there.Well using unique_ptr too is secure in this matter.Consider that a function returns an address of a dynamic storage.If on the calling side if the caller forgets to delete the storage then there will be a memory leakage.

int* func( )
{
int *i=new int(90) ;

///Do anything here

return i ;
}

If the caller is responsible enough to delete the storage then the program is secure.But still there is a situation in which the region containing the deleting code is never reached.This case happen if somewhere in the middle an exception is thrown.When an exception is thrown the program will terminate and the remaining code is never executed.Consider the functions below.

int* func( )
{
int *i=new int(90) ;

///Do anything here

return i ;
}

void Count( int i )
{
i=890;

///…

throw(90) ; //exception thrown here
}

void TestFunc( )
{
int *newMem=returnIntPtr( );

Count( newMem ) ; ///throws exception

delete newMem ; ///never executed
}

The existance of the chances that an exception will be thrown does not make the above program secure.Another way of looking at it is that the raw pointer is not versatile enough to handle the dynamic storage securely in all possible ways.So this makes raw pointer unfit for utilization in such programming design.If unique_ptr is used instead of the raw pointer there should be no issue here even if an exception is thrown.The unique_ptr will make sure that the storage is deleted during run-time and so even in the midst of untimely ending of execution you can sit back and relax.

int* func( )
{
int *i=new int(90) ;

///Do anything here

return i ;
}

void Count( int i )
{
i=890;

///…

throw(90) ; //exception thrown here
}

void TestFunc( )
{
unique_ptr<int>up=static_cast<unique_ptr<int>>( func( ) ) ;

Count( *up ) ; ///throws exception
}

Now the address returned by func( ) is assigned to unique_ptr,so here if count() throws an exception the memory will still be deleted safely.


Using unique_ptr instead of raw pointer as a data-member class

Suppose you have a class in which one of the data member should be a pointer and it must handle a dynamic storage.Well you can make that pointer a raw pointer and in the destructor function you can add the deleting code.Such design is safe because the destructor is called every time the object goes out of scope .However,it is safer and more efficient if the pointer used is unique_ptr instead of the raw pointer.

class A
{
int *i ;

public:
A(int *ii=nullptr ):i(ii) { }

~A( ) { delete i; }
} ;

///Using unique_ptr
class A
{
unique_ptr<int>i ;

public:
A(int *ii=nullptr ):i(static_cast<unique_ptr<int>>(ii) ) { }

~A( ) { }
};

int main( )
{
A a(new int(90) ) ; ///Declaring object,works fine for both the A class shown above

return 0;
}

Using unique_ptr prevent the need for adding a storage deleting code in the destructor .And so if there are more than one pointers,there is no need to trouble ourself by not forgetting to add a deleting code for each of them.The another property introduced in your class by unique_ptr is the objects are not shareable.

int main( )
{
A a(new int(67) ) , a1 ;

a1=a ; ///error not shareable

return 0 ;
}

Calling a copy constructor is also not allowed because copying means sharing the object.However,you can make the object moveable and this process is faster that sharing the object because the object is simply moved and there is no need for creating a new storage and copying the value.But to make the object moveable you must add a move assignment operator to the class and how this is done is shown below.

Note::I won’t be discussing about moving an object in detail here,another post is dedicated for this topic.

class A
{
unique_ptr<int>i ;

public:
A(int *ii=nullptr ):i(static_cast<unique_ptr<int>>(ii) ) { }

A& operator=(A &&m ) noexcept
{
if(this== &m )
{
return *this ;
}
up=std::move(m.up);
m.up=nullptr ;

return *this ;
}

~A( ) { }
};

int main( )
{
A a( new int(89) ) , a1 ;

a1=a; ///error not shareable

a1=std::move(a) ;///works fine

return 0 ;
}

Now you can move the object and note (I am repeating it again) moving an object is faster than copying the object.


Limitation of unique_ptr

The limitation of unique_ptr is visible when a raw pointer is assigned to two different unique_ptr.In such scenario when one of the unique_ptr goes out of scope it would have deleted the storage and the other would be pointing to some invalid storage.This also means when the other pointer goes out of scope it will try to delete the same storage again which is in fact a dangerous action.Consider the code below.

int *iMem=new int(893) ;

unique_ptr<int> up=static_cast<int>(iMem) ;

{
unique_ptr<int> up1=static_cast<int>(iMem) ;
}///storage deleted here

cout<< *up ; ///undefined

///when program ends up1 will try to delete the storage again

The best advice to protect against such error is never use raw pointer to assign to any unique_ptr.


Related Link

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

->3 points why shared_ptr and unqiue_ptr constructor is made explicit?