C++11 allocator : what is?


C++11 allocator class

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 and the number of elements the array will hold is specified inside the square bracket.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 process involve:

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 some times it happen that all the three objects may not be utilized yet and some may be used later on.In such case initializing all the three objects is not necessary.Only those objects which may be utilized then should be initialized.By avoiding unnecessary initialization of objects the overhead produce by unnecessary constructor call is avoided.

The C++11 allocator class template allows a more efficient handling of array type object by allocating space for the objects without calling it’s 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 it’s creation and producing unnecessary overhead due to the constructor call.

In this post we shall explore the features of allocator class and also discuss how to use them in our program.


Allocator class template

The allocator template class being a template require that a type is passed as 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’ denoted here is of ‘size_t’ type and it represent the number of objects for which the allocation should be made.After the function is called it returns a pointer to the memory and so this 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.

mem[0]=78;
mem[1]=890;
mem[2]=899;

/***
or
***/

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.

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

all.deallocate(mem , 3 ) ;

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





Copying allocator content

We can perform copying of one allocator object to another.Copying of allocator object is same as copying the value contain in the memory allocated by the allocator object.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 of 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’/span>

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

al.deallocate(mem ,3);


Different allocator objects of one type are same.

Different allocator objects of one type are same and the allocator objects of different types are also interchangeable.By ‘same‘ we mean different allocator objects can manipulate the memory allocated by another allocator object of the same type.If the type is 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 same type manipulating the memory allocated by another object.

allocator<int> al , al1 ; //allocator objects of int type
allocator<string> 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

cout<< i[1] << ” ” << 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 also access that memory since they are of int type.In case of alSt,since it is string type object it cannot construct or deallocate the memory allocated for int type.

Interchanging allocator object of different type

To interchange the allocator objects of different types we will use the member templates rebind and it’s syntax is,

allocator_type::rebind::other

Note it is not a function but a member template.

allocator<int>al ;

decltype(al)::rebind<string>::other sta , sta1 ; /*string type allocator
objects obtained from int type allocator object */

string *st=sta.allocate(2) ;

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

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


 



Member functions of allocator() template class

Some of the member functions of allocator class are:

i)address.

ii)allocate.

iii)deallocate.

iv)max_size.

v)construct.

vi)destroy.

There are two non-member functions.

i)operator==() and

ii)operator!=().

All the member functions are discussed below.

address()

This function will accept a value/variable as an argument and return the address of that value.The address obtain is same as the address value obtain by using the ‘&‘ operator.So you can even pass a random variable not belonging to the array element allocated by the allocator object.

allocator<int> al ;

int *mem=al.allocate(2) ;

mem[0]=90 ;
mem[1]=404 ;

cout<< al.allocate(*mem) << endl
  << mem ;

in ival=890;

cout<< al.address(ival) << endl //Passing random variable
  << &ival ; //output is the same as above

al.deallocate(mem , 2) ; //do not forget!


max_size()

This function returns the maximum size the allocator can allocate.

allocator<string>alSt;

cout<< alSt.max_size() << endl ;

 


allocate( )

This function will allocate memory in the heap.The amount of memory allocated is “n * sizeof(type)” ,where ‘n’ is the argument passed and it denotes the number of array elements.Note if the type is class it does not call the constructor ,it only allocate memory.

class A {};

allocator<A> alA;

A *a=alA.allocate(2); ///does not call the default constructor

Here since the constructor is not called initialization is not performed ,it is performed by calling the function construct().


construct( )

If the type is built-in this function will store the value in the memory mentioned as the first argument.And if the type is class the constructor is called to performed the initialization.If any constructor is not provided it calls the default constructor.
 

class A { };

allocator<A> alA;

A *a=alA.allocate(2);

alA.construct(a , A) ;///default constructor is called
alA.construct(a+1 ,A) ;///default constructor is called

alA.deallocate(a,2);

 


destroy( )

If the type is a class then this function will call the destructor of the class.For built-in type this function does nothing.

A *a=alA.allocate(2); //does not call the default constructor

alA.construct(a , A()) ;//default constructor is called
alA.construct(a+1 ,A(90)) ;//default constructor is called

alA.destory(a) ; //default destructor called
alA.destory(a+1) ; //default destructor called

cout<< a[0].

alA.deallocate(a,2);

/*****
Calling destory() for built-in type
*****/

allocator<double> alDb ;
double *dbMem=alDb.allocate(2) ;

alDb.allocate(dbMem, 9.098) ;
alDb.allocate(dbMem ,789 ) ;

alDb.destroy(dbMem );
alDb.destroy(dbMem+1) ;

cout<< dbMem[0] << endl //print 9.098
<< dbMem[1] ; ///print 789

dbMem[0]=980.8 ;
dbMem[1]=.048 ;

abMem.deallocate(dbMem , 2);

 


deallocate()

This function will free the storage allocated in the heap.The allocate() function does not allocate storage using the array new operator.It uses the normal new operator to allocate the storage and return the pointer to that memory.In our program the address is usually assigned to the raw pointer.Using the raw pointer and the delete operator we can also free the storage without actually calling the deallocate() function.

allocator<double> alDb;

double *dbMem=alDb.allocate(2);

alDb.deallocate( dbMem ,2 ) ;///storage deleted

//::operator delete(dbMem) ; ///also work fine

The storage will be deleted safely by also calling the ::operator delete().


operator==() and operator!=() functions

As stated earlier the objects of allocator class of similar type are the same and the objects of different types are interchangeable.So the function operator==() will always yield true and the function operator!=() will always yield false.

allocator<int> iAl ,iAl1 ;

allocator<string> sAl ,sAl1 ;

allocator<A> alA ;

cout<< (iAl==iAl1) << endl ///give true<
  << (alA==iAl) ; ///give true

cout<< (iAl1!=aAl) << endl ///give false
<< ( alA!=sAl1 ) ;///give false





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 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<class Tp>
class allocator
{
public:

///constructor
allocator() {}

///alocate() function
allocate(size_t n)
{
 return static_cast<Tp*>(::operator new( n* sizeof(Tp)) );
}

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

///destroy() function
void destroy( Tp* ptr )
{
ptr->~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<int<al;
int *i = al.allocate(2);

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

cout<< i[0] << ” ” << i[1] << endl;

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

cout<< i[0] << ” ” << i[1] << endl;

al.deallocate(i, 2) ;

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

allocatorr<A>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;
}

 


Related Link

C++11 allocator_traits uses

->Dynamically allocating memory in C++ with new and delete operators.

->How to use placement new and placement delete operators in C++.