3 points why shared_ptr and unqiue_ptr constructor is made explicit?


The constructors of the smart pointers :shared_ptr and unique_ptr are made explicit.If you need a prove of this and if you are using Code::blocks MinGw compiler you can check the constructor defined in the file ‘shared_ptr.h‘(line number 117) found in the directory “C:\CodeBlocks\MinGW\lib\gcc\mingw32\4.7.1\include\c++\bits” and for the unique_ptr constructor definition go to the file ‘unique_ptr.h‘(line number 119) found in the same directory.You can see the keyword “explicit” added in front of the constructor name in each of these template class constructor.Making a constructor explicit set up some boundary on how the data-member can be initialized for instance,only direct initialization method can be used to initialized the object and not any other method.
 

shared_ptr<int> sp( new int(90) ) ,  //work fine
  sp1=new int(89) ;  ///Error

unique_ptr<int> up=unique_ptr<int>(new int(90) ) , ///work fine
  up1=new int(89) ; ///Error

 
The method of initialization which gives an error is known as indirect initialization method.And in this method the smart pointer is assigned a raw pointer returned by the new operator while, in the direct initialization method the raw pointer is passed as an argument to the constructor.Well in this post the method of initialization which works and which does not work is not the main topic of discussion,the more important question i.e. why is the constructor made explicit? will be discussed here.So far I have discovered 3 reasons(more may exists) why the constructor is made explicit.


First::To prevent implicit conversion

 
To understand what is implicit conversion (it is also explained in detail in Chapter 4) let us look at the class below.
 

class Data
{
int *i;

public:
Data(int *ii=nullptr):i(ii) { cout<< “Constructor”; }

~Data( ) { delete i ; }
};

 
The class constructor is not explicit so such class object will accept indirect initialization.
 

int main( )
{
Data dat=new int(89) , ///work fine
   dat1( new int(76) ) , ///work fine
   dat2=Data(new int(78) ) ;

return 0 ;
}

 
The first initialization method works because the compiler is allowed to perform implicit conversion of raw pointer to Data object before constructor is actually called.Meaning the raw pointer returned by the new operator will have it’s own temporary Data object constructed first and after that the object is assigned to dat object.So the first initialization method is same as,
 

Data dat=Data(new int(89) ) ;

 
Which is also same as the third initialization method.So you see for an indirect initialization to work the compiler will perform an implicit conversion first.
 
If the keyword explicit is added the compiler is prohibited from performing any implicit conversion and hence the indirect method will not work.
 

class Data
{
int *i ;

public:
explicit Data(int *ii=nullptr):i(ii) { cout<< “Constructor”; }

~Data( ) { delete i; }
};

int main( )
{
Data dat=new int(89) , //Error
dat1=Data(new int(78) ) ; ///work fine

return 0;
}

 
The smart pointer classes have their constructor made explicit like the class Data so indirect initialization of the objects does not work.



Second::Code optimization.

 
The second reason follows directly from the first.If the compiler is allowed to carry out an implicit conversion it will generate an overhead unnecessarily.In the class like Data the overhead is produce during the conversion from raw pointer to temporary Data object.Since there is only one implicit conversion during the class object initialization the overhead produced may seem negligible but mind you it will become noticeable if many such temporary objects is created as more and more operation is performed by the program using that class.And one such operation is when a function returned a raw pointer and it is initialized to a Data object.
 

int* newStorage( int i )
{
return new int(i) ;
}

int main( )
{
Data datObject=newStorage(567) ; ///overhead produce here

return 0 ;
}

 
If a smart pointer manage the object of the class Data and if the above same operation is performed,you will absolutely get an error.
 

int main( )
{
shared_ptr<Data>spDat=newStorage(567) ; ///Error

return 0 ;
}

 
Since shared_ptr or any of the smart pointer does not allowed implicit conversion or in other words,since the constructor is made explicit there is no risk of producing any overhead whenever you initialized a smart pointer class object or if any other operations is carried out.Even if a function-like the newStorage() function-should return a pointer it must return the smart pointer of the type and not any raw pointer.By putting up a strict rule of prohibiting any implicit conversion smart pointers makes sure that no overhead -even if negligible- escapes from the program in any way.Hence,the program using smart pointer gets optimized.
 


Third::Direct initialization is a better method.

 
The smart pointers especially shared_ptr and unique_ptr allows the programmer to manage the storage deletion using a deleter function of there own.The deleter function is deploy by passing the function name as a second argument during the initialization process of the pointer.
 

void deleterFunc(int *i) ///deleter function
{
delete i ;
}

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

unique_ptr<int , decltype( deleterFunc )*> up(new int(89) , deleterFunc ) ;

return 0 ;
}

 
Suppose if we are to use the indirect method of initialization,passing the deleter function would not be possible.In fact,there is no known syntax in the whole of C++(including C++11 and C++14) which allow passing of two arguments during object initialization using the indirect approach.Now this can rather create an annoying situation.Consider that the constructor of shared_ptr and unique_ptr is not explicit and a shared_ptr object is initialized by a function returning a raw pointer.If we want to deploy a deleter function for the shared_ptr object there is no way of passing the function name to the shared_ptr class.In such desperate situation we have to either drop the function name or use the direct approach.
 

///Supposing shared_ptr support implicit conversion

int* func( )
{
return new int(89) ;
}

int main( )
{
shared_ptr<int>sp=func() ; ///Cannot pass the deleterFunc

shared_ptr<int>sp1=shared_ptr<int>( func( ) , deleterFunc ) ; ///work fine
unsique_ptr<int , decltype( deleterFunc )*>up(new int(89), deleterFunc ); ///work fine

return 0 ;
}

 
Well you can see the advantage hold by the direct approach for object initialization over the other approach .And considering all the overhead and and headache the indirect approach might render,don’t you think it is
better that the constructor of the smart pointer should just be made an explicit without any questions asked.
 


Related Link

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

->Unique_ptr: what makes it unique?