C++11 allocator : optimized way to allocate storage

The C++11 allocator class what is its purpose? how is it a better way to allocate memory? we will answer some of these questions here. The picture below shows a brief working of the C++11 allocator class.

C++11 allocator class


The ‘new’ operator

The ‘new operator’ provides us a way to allocate memory in the Heap. Using new, if we want to allocate an array of memory in the Heap we append the square bracket with the size of the elements the array will hold. An example code is given below.

class A{
 int val;

public:
 A(int i):val(i) { }
 ~A() { }
};

int main()
{
int *memPtr=new A[3] ;

A[0]=A(90) ;

return 0;
}

Whenever we call the new operator, there are two processes involved:
 
i)A memory for the array elements is allocated.
 
ii)After each allocation the memory is initialized by calling the constructor; if the initializer is not provided the default constructor is called.

In the code above we have created three objects and initialized them by calling the default constructor, but sometimes it happens that all the three objects may not be necessary yet, some may be used later on. In such case initializing all the three objects is not necessary. Only those objects which would be utilized currently should be initialized. By avoiding unnecessary initialization of objects the overhead which might be produced by the constructor call is avoided.

The C++11 allocator class template allows more efficient handling of array type object by allocating space for the objects without calling its constructor to perform the initialization. We can initialize the object later when it is about to be utilized. No doubt, such implementation is much more efficient than initializing all the objects at one go during its creation and producing unnecessary overhead due to the constructor call.



C++11 Allocator class

The allocator class is a template so it requires that a type is passed as a template argument during the object creation. By declaring an allocator object we have not allocated the memory yet. To allocate the memory we have to call the function allocate(n). The ‘n’ is of ‘size_t’ type and it represents the number of objects the allocation is made. After the function is called it returns a pointer to the memory allocated. This returned pointer must be assigned to another raw pointer. Consider the program below.

allocator<int>all ; //allocator object

int *mem=all.allocate(3) ;
/*memory allocated for three integers; the memory is not initialized yet*/

To initialize the memory we can either use the normal array syntax or we can call the construct() function of the allocator class. If we use the construct() function we have to pass two arguments: the first argument is the address and the second argument is the initializer.

//Initialization of 'mem' memory 
mem[0]=78;
mem[1]=890;
mem[2]=899;

/***
or Initializing using construct()  ***/
all.construct(mem , 78 );
all.construct(mem+1 , 890 );
all.construct(mem+2 , 899 );

cout<< mem[1] ; ///prints 890

Both the initialization methods work fine but if the type is string then use the construct() function to initialize the memory.

Deallocating the memory

The memory allocated by the allocate() function is in heap which means we must not forget to delete the storage before the program terminates. The storage can be deleted using the function deallocate(). This function will accept two arguments: the first argument is the pointer to the array memory and the second is the number of the array objects.

Link :C++ allocator allocate() function

all.deallocate(mem , 3 ) ;

Beware ! forgetting to call the deallocate() function can lead to memory leakage.



Copying allocator content

We can also perform copying of allocator object to another object. Copying of allocator object is same as copying the value contain in the memory. To perform copying we will not use the assignment operator ‘=’ or call the copy constructor, instead we will use the function uninitialized_copy_n(). This function will accept three arguments:
 
->The first argument is the array from which the value will be taken.
 
->The second argument is the number of elements to be copied and
 
->The third is the pointer to the destination memory where the value will be copied.

int arr[3]={3,4,5} ;

allocator<int>al;

auto *mem=al.allocate(3) ;

uninitialized_copy_n(arr, 3 , mem) ; //copies content of ‘arr’ to ‘mem’

cout<< mem[2] << ” ” << arr[2] ; //output is same

al.deallocate(mem ,3);

Different allocator objects of one type are same.

Different allocator objects of b<>one type are the same and the allocator objects of different types are also interchangeable. By ‘same‘ we mean different allocator object can manipulate the memory allocated by another allocator object of the same type.

If the type are different you can’t manipulate the memory. But the objects of different type can be converted to another type .The code below shows the objects of allocator of the same type manipulating the memory allocated by another object.

Link :C++11 allocator construct() fucntion

allocator&amp;amp;amp;lt;int&amp;amp;amp;gt; al , al1 ; //allocator objects of int type 
allocator&amp;amp;amp;lt;string&amp;amp;amp;gt; alSt;

int *i=al.allocate(4) ; //al allocates memory to store 4 integers

al.construct( i, 78 ); //al initialize ‘i’ by a calling the construct() function 
al1.construct(i+1, 909 ) ;//al1 initialize i+1 by calling the construct() function 
al.construct(i+2 , 606 ) ; //al initialize i+2 by calling the construct() function
al1.construct(i+3 , 404 ) ;//al1 initialize i+3 by calling the construct() function

//alSt.construct(i+3 , 404) ; //error! different type 

cout&amp;amp;amp;lt;&amp;amp;amp;lt; i[1] &amp;amp;amp;lt;&amp;amp;amp;lt; ” ” &amp;amp;amp;lt;&amp;amp;amp;lt; i[2] ;

al.deallocate( i,4 ) ;

al1.deallocate( i,4 ) ; //also work fine

//alst.deallocate( i,4 ) ; error!

You can see that although ‘al’ allocated the memory pointed by ‘i’, al1 can still access that memory since they are of int type. In case of alSt, since it is a string type object it cannot construct or deallocate the memory allocated for int type.


Interchanging allocator object of different type

To interchange the allocator object of different types we will use the member templates rebind and its syntax is,

Link :C++11 allocator deallocate() function

allocator&amp;amp;amp;lt;int&amp;amp;amp;gt;al ;

decltype(al)::rebind&amp;amp;amp;lt;string&amp;amp;amp;gt;::other sta , sta1 ; 
/*string type allocator objects obtained from int type allocator object al*/

string *st=sta.allocate(2) ;

sta.construct( st, “New” );
sta.construct( st+1, “Newer” );

sta1.deallocate( st,2 ) ; //work fine

The meber fucntions of allocator class is discuss here C++ allcoator class member functions


The simplest structure of allocator class

In this section we will try to reproduce the simplest implementation of the allocator class template. By knowing how the allocator class is written we will be able to see how it function and note the class template code given here may not be exactly same as the allocator class template utilized by your compiler. Meaning your compiler allocator class will have more member types and member functions, but the basic functionality given here will be the same; optimization may be also required for the code given here.

template&amp;amp;amp;lt;class Tp&amp;amp;amp;gt;
class allocator
{
public:

//constructor
allocator() {}

//alocate() function
allocate(size_t n)
{
 return static_cast&amp;amp;amp;lt;Tp*&amp;amp;amp;gt;(::operator new( n* sizeof(Tp)) );
}

//construct() function
void construct(Tp* memPtr , const Tp &amp;amp;amp;amp;val )
{
  ::new((void)memPtr) Tp(val) ; ///placement delete called
}

//destroy() function
void destroy( Tp* ptr )
{
ptr-&amp;amp;amp;gt;~Tp() ; ///calls destructor
}

//deallocate() function
void deallocate(Tp* ptr , size_t)
{
 ::operator delete(ptr) ;
}

//destructor function
 ~allocator() { }
};

Using the class above test the code given below. I am sure it will work smoothly.

Note while using the class template given above change the class name allocator to some other name -say allocatorr- otherwise you will get an error “reference to ‘allocator’ is ambiguous”. This is due to name conflict with the allocator class name of the standard library.

int main( )
{
allocator&amp;amp;amp;lt;int&amp;amp;amp;lt;al;
int *i = al.allocate(2);

al.construct(i, 67);
al.construct(i+1 , 6789 );

cout&amp;amp;amp;lt;&amp;amp;amp;lt; i[0] &amp;amp;amp;lt;&amp;amp;amp;lt; ” ” &amp;amp;amp;lt;&amp;amp;amp;lt; i[1] &amp;amp;amp;lt;&amp;amp;amp;lt; endl;

al.destroy(i);
al.destroy(i+1);

cout&amp;amp;amp;lt;&amp;amp;amp;lt; i[0] &amp;amp;amp;lt;&amp;amp;amp;lt; ” ” &amp;amp;amp;lt;&amp;amp;amp;lt; i[1] &amp;amp;amp;lt;&amp;amp;amp;lt; endl;

al.deallocate(i, 2) ;

/***
testing with class type
***/

allocatorr&amp;amp;amp;lt;A&amp;amp;amp;gt;alA;
A *a=alA.allocate(2) ;

alA.construct(a , A() );
alA.construct(a+1 ,A() );

alA.destroy(a);
alA.destroy(a+1);

alA.deallocate( a,2) ;

cin.get() ;
return 0;
}