C++ best way to overload new and delete operators


In chapter 5 we have seen how to overload functions and operators.In this chapter we will see how to overload the operators new and delete.

Writing an overloaded new and delete operators function is not hard but writing a refined overloaded new and delete operator function is a bit hard.By refined version I mean an efficient new and delete operator function.Efficiency of a new and delete operator is defined by faster allocation and deallocation,a better system of memory usage:for instance the data should be aligned to one another in the memory,it must be able to detect error in memory usage and so on.To make our function have the said criteria we must know a lot more in and out features of C++ and we must be able to code better,so it belong to a category of higher level programming.But everything starts from basics and after having cleared the basics we can undertake the advanced stuff.For newbie programmer,in this post we will discuss the most basic way of writing an overloaded new and delete operators function.


Overloading new

Our new overloaded operator function like the original version(the new provided by the compiler) should handle memory allocation.We can go about to do this in two ways :

i)By using the C memory allocation function malloc( ) included under <cstdio> library.

ii)By using the global new operator.Don’t get confuse here by the term ‘global new operator‘, it simply refers to the original new operator provided by the compiler.

The syntax of writing an overloaded new is same as the other operators discuss in chapter 5.The return type followed by ‘operator new’ and bracket.The return type is strictly void* and the function must accept one and only one argument of type size_t(unsigned int). An overloaded new using malloc( ) and the global new is shown below.

///Using malloc( )
void* operator new(size_t sz)
{
void* mem=malloc( sz );   ///returns NULL if memory allocation failed

if( !mem )   //checking if memory allocation is successful
{
cerr<< “Allocation failed “;
exit( ) ;
}

return mem ;
}

///using the global new
void* operator new(size_t sz)
{
return ::operator new( sz ) ;
}

Link : C++ cstdlib exit

Note in the second function double colon(::) is included before the operator new.This signify that we are calling the global new operator.


Overloading delete operator

The overloaded delete must also like the original delete operator handle memory deallocation .If malloc() is used to allocate memory we must use free() in the overloaded delete function because free() does the opposite of malloc() i.e. freeing memory .But if global new is used then a global delete operator must be called to deallocate the storage.The syntax of overloaded delete operator function is also same as the other operator.The return type here is void meaning it returns nothing and it must accept only one argument of type void* :this pointer holds the address of the memory which is to be deallocated.

///using free()
void operator new(void *memPt)
{
free( memPt ) ;
}

//using global delete
void operator delete(void *memPt)
{
::operator delete(memPt) ;
}

The program below shows the implementation of an overloaded new and delete operator as a member function.

class Test
{
int i ;

public:
 Test(int ii=0):i(ii) { }

 void* operator new(size_t sz )
 {
 void *vd=malloc(sz) ;

 if(!vd)
 {
 cerr << “Allocation failed \n”;
 exit() ;
 }

 return vd;
 }

 void operator delete(void *vd)
 {
 free(vd);
 }

 ~Test( ) { }
};

int main( )
{
Test *t=new test(34) ;

delete t ;

cin.get( ) ;
return 0 ;
}

Here is another program which implement the overloaded new and delete but using the global new and delete operator to allocate and deallocate the storage.Look carefully at the two types of global operator new and delete shown in the function.

class Test
{
int i ;

public:
 Test(int ii=0):i(ii) {}

///Operator new
 void* operator new(size_t sz)
 {
 return ::operator new(sz);   ///works fine

 ///return ::new Test() ; ///you can also use this,works fine
 }

///Operator delete
 void operator delete(void *memPt)
 {
 ::operator delete(memPt) ;

 //::delete memPt; //this also works fine
 }

 ~Test( ){}
};

int main( )
{
Test *t=new test(34) ;

delete t ;

cin.get( ) ;
return 0 ;
}

The two types of global operator new and delete used in the program above work just perfectly fine although their syntax differ a bit.The first global operator new has the word ‘operator‘ in front of the keyword ‘new‘ whereas the second does not and this difference also apply to the two global delete operator.

In spite of the syntax differences,the two types of global new and the two types of delete operator seem to perform the same task: allocating and deallocating the memory.So can we say they are the same? no! and yes!,No,for the global new operator because there exist some internal differences between them but the two global delete operator functions are the same i.e deallocating memory and nothing else.The difference between the two new operator is discuss below.





Difference between ::new int() and ::operator new(size_t).

Needless to mention the first obvious difference between the two global new operators is their syntax.In one of the syntax the keyword ‘operator’ is used whereas in the other the data type for which the dynamic storage should be made is mentioned.This difference is not merely a syntactic difference with the same meaning.In the next
difference explained below you will see how mentioning of the data type impact the program differently from the other syntax where it is not used.

The second difference between the two new operators is based on the data type for which the allocation is made.Consider the simple code for allocating a dynamic storage for the int type.

int *i=new int(78 ) ; ///calls ‘::new int()’ overloaded version

The interpretation here is allocate memory that can hold integer type. Now this means the memory allocated is meant to store only integer data type.Also allocating dynamically for the other data types in this way is meant to work only for that type.But if we look at the other operator new the data type is not mentioned at all.This mean the memory storage allocated using the statement ‘operator new(size)‘ is meant to hold a data that has no type.So, basically the syntax operator new(size_t) will allocate a raw chunk of memory and return a pointer of void type.

void *vdMemPt=operator new( sizeof(char)*4 ) ; //calls ‘::operator new(sizeof(char)*4)’ overloaded version
string *stPt=new string(“New”) ;

memcpy(vdMemPt, stPt , (sizeof(char)*4)) ;

cout<< *(static_cast(vdMemPt)) ; //copy content of stPt to vdMemPt

There is another difference resulting from the above difference.Whenever we overload the new operator and we use the global new operator that mentioned the type- :new class_name( ) to allocate memory the class constructor is always called.This is no surprising because whenever a compiler allocate memory it will always try to initialized it and constructor does the work of initialization,hence the constructor call.The other operator ::operator new(size_t) allocate a memory of void type and so the compiler does not know which class constructor to call or the compiler cannot find any matching constructor to call.So there is no constructor call in this case.

class Test
{
int i ;

public:
 Test(int ii=0):i(ii) {}

///Operator new
 void* operator new(size_t sz)
 {
 return ::operator new(sz);

 ///return ::new Test() ; ///check using this also
 }

///Operator delete
 void operator delete(void *memPt)
 {
 ::operator delete(memPt) ;
 }

 ~Test( ) {}
};

int main( )
{
Test *t=new test(34) ;

delete t ;

cin.get( ) ;
return 0 ;
}

Run the program above and check the output so that you can have a better grasp of the differences explained.



Which version of new operator should I use?

Knowing the differences between the two new operator types cannot make you contented because if you ponder over them you are liable to end up asking the question,which version should I use and when? The answer is pretty simple but before I answer the question consider the program given below.

class Test
{
int i ;

public:
 Test(int ii=0):i(ii) {
 cout << i << endl
    << “Constructor” ; }

///Operator new
 void* operator new(size_t sz)
 {
 return ::operator new(sz);   ///constructor not called here
 ///return ::new Test(); ///constructor is call here
 }

///Operator delete
 void operator delete(void *memPt)
 {
 ::operator delete(memPt) ;
 }

 ~Test( ) {}
};

int main( )
{
Test *tMem=new test(34) ; ///constructor called here

delete tMem ;

cin.get( ) ;
return 0 ;
}

If you run this program you will notice that when we use ::new Test() the constructor of Test is call twice but if ::operator new(size_t) is used the constructor is called only once.For the syntax that mentioned the data type(::new Test()) the first constructor call happens when the memory is allocated i.e when ::new Test() is executed.The second constructor call happens when the pointer to the memory returned by the ‘operator new’ function is assigned to the class pointer tMem.Note this second constructor call is the only constructor call that occur when the syntax ::operator new() is used. And it is only during this constructor call that the actual initialization of the memory occur.This means the syntax ‘::new Test()’ produce and unnecessary overhead of constructor call (the first call) at each allocation process while the syntax ‘::operator new(sz)’ does not-because the first call is avoided.The overhead produced can slow your program down if allocation and deallocation occur frequently in the program. Not only that if a class has another class object as one of it’s data members then at every memory allocation process there will be an overhead of two constructor call:one for the data member class object and the other for the class object.Now you can be sure that your program will get even slower.So if you are overloading new and global new operator to allocate memory for class object then use the ‘::operator new(size_t)‘ version.But if you require a dynamic storage for your normal use-for built-in data type- then use the other version:’::new data_type()‘.

To verify that an overhead of two constructor call occur when ‘::new Test()’ is used and when one of the class data members is an object you can try running the program given below.

//Class small
class Small
{
int i ;
public:
 Small(int ii=0):i(ii) {cout<<“Small constr\n”; }
 ~Small() { }
};

//class Big
class Big
{
int i ;
Small s ;

public:
 Big(int ii=0):i(ii),s(ii) {
 cout << i << endl
    << “Big Constr\n” ; }

///Operator new
 void* operator new(size_t sz)
 {
&nsbp;return ::new Big( ) ;
 }

///Operator delete
 void operator delete(void *memPt)
 {
 ::operator delete(memPt) ;
 }

 ~Big( ) {}
};

int main( )
{
Big *Mem=new Big(300) ;

delete Mem ;

cin.get( ) ;
return 0 ;
}

The output is,

Small constr
0
Big Constr
Small constr
300
Big Constr

The Small constructor is call twice,the Big constructor is also call twice.Note in the first constructor call the value of ‘i’ is 0,meaning it is not initialized with 300.Only in the second constructor call the data member ‘i’ is initialized with 300.


 



Overloading new and delete array

New and delete operator array like the operator new and delete have the same returned type ,argument type and the number of parameters the function should accept. However,you must add a square bracket([]) after the keyword new and delete in the function name.The square bracket signify the arrayness of the storage that the operator new and delete would work on.How to overload the new and delete operator array is shown below.

void* operator new[](size_t sz)
{
return ::operator new(sz);
}

void operator delete[](void *memPt)
{
::delete []memPt ;
}

To allocate a storage for an array the normal ‘operator new(sz)’ is used,but to delete the array storage the square bracket is added in front of the memory pointer.If the square bracket is not added then only the storage of the first array will get deleted and there will be a memory leakage here.


Placement new and placement delete

Sometimes you will come across an overloaded new and delete function accepting two or more arguments,such function are not treated as the normal new and delete operator and they are given a different name:placement new and placement delete.They don’t allocate memory in the heap rather they utilized the memory in the stack which is allocated for another data during the compiler-time.How is it done and how do we handle placement new and placement delete is discuss in detail in another post,the link is https://corecplusplustutorial.com/placement-new-and-delete/.


Related Link

->Dynamically allocating memory in C++ with new and delete operators.
 
->New,delete operators and class object.
 
->Void pointer and new , delete operators.

->Overloading placement new and placement delete operators