3 points why shared_ptr and unqiue_ptr constructor are made explicit?

The constructors of the smart pointers: shared_ptr and unique_ptr are made explicit. Here we will see 3 points why shared_ptr and unqiue_ptr constructor are made explicit.

Link :C++11 shared_ptr
Link :C++11unique_ptr

If you need a prove of shared_ptr and unique_ptr constructors being explicit and if you are using Code::Blocks MinGw compiler you can check the constructor defined in the file ‘shared_ptr.h‘ found in the directory “..\MinGW\lib\gcc\mingw32\4.7.1\include\c++\bits” and for the unique_ptr constructor definition go to the file ‘unique_ptr.h‘ found in the same directory. You can see the keyword “explicit” added in front of the constructor name in each of these template class. 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 initialize 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.

In the direct initialization method the raw pointer is passed as an argument to the constructor. Well, in this post we are trying to find out why the constructor is made explicit? and not which type of initialization works and which does not. So then let’s get to it. So far I have discovered 3 reasons(more may exist) 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 the raw pointer to Data object before the constructor is actually called. Meaning, the raw pointer returned by the new operator will have its own temporary object of Data type 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 are created as more and more operation is performed by the program. 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 allow implicit conversion there is no risk of producing any overhead whenever you initialized a smart pointer object. Even if a function -like the newStorage() function- should return a pointer it must return a smart pointer of the type and not any raw pointer.

By putting up a strict rule of prohibiting any implicit conversion, smart pointers make 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 the passing of two arguments during object initialization using the indirect approach.

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 a 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 advantages held by the direct approach of object initialization over the other approach. And considering all the overhead 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.