lundi 8 septembre 2008

Construction d'objets sur un emplacement de mémoire défini par l'utilisateur en C++

Comment construire un objet sur un emplacement dont vous connaissez l'adresse, et quelques informations sur la surcharge des opérateurs new et delete.

Il peut arriver lorsque l'on veut construire un objet en C++ que l'on ne veuille pas le construire là où l'opérateur new par défaut allouera la mémoire, mais à un emplacement déjà connu. Par exemple cet emplacement peut être dans un "pool" de blocs de mémoire de la bonne taille, ou une adresse spécifique pour accéder à des ports d'entrée/sortie (ce qui permet de mapper les membres de la classe sur du hardware), ou encore une adresse renvoyée par un allocateur de mémoire "fait maison". Voici la syntaxe pour construire un tel objet :

[cpp]
void *address //address where you want to construct your object

class A
{
public:
A(int parameter, bool anotherParameter); //the constructor you want to use
~A();
};

A *a = new(address) A(1, false); //constructs an instance of A at the location pointed by address
a->~A(); //destructs a without freeing up the memory
Voilà, il n'y a rien d'autre à faire pour cet exemple précis, mais la mécanique utilisée (la surcharge de l'opérateur new) permet d'autres choses.

Le principe est en fait de ne pas utiliser l'opérateur new par défaut, mais un opérateur surchargé qui prend un paramètre supplémentaire, ici de type void*. Il existe par défaut un tel opérateur new qui construit l'objet sur l'emplacement pointé par ce paramètre (on appelle cet opérateur "placement new"), mais il est possible de le surcharger :

[cpp]
void *operator new(size_t s, void *address) { return address; }

Vous remarquerez que le premier paramètre est de type size_t. C'est obligatoire, il s'agit de la taille de l'objet à allouer. Dans cet exemple, le deuxième paramètre est de type void*, mais ça pourrait être n'importe quoi d'autre, et on peut rajouter d'autres paramètres selon son bon plaisir. La valeur de retour doit être de type void* et pointe sur l'endroit où sera construit l'objet. Enfin, si jamais une exception intervient pendant la construction de l'objet, l'opérateur delete correspondant au new qui a été utilisé sera appelé pour désallouer la mémoire. Dans notre cas, il s'agira de

[cpp]
void delete(void *obj, void *address) { }

qui ne fera en fait rien, car il n'y a rien à désallouer. Ici aussi, le premier paramètre est obligatoire, il s'agit de l'adresse qui avait été renvoyée par le new. Les paramètres suivants doivent être du même type que ceux du new. Attention toutefois : si la construction de l'objet se déroule correctement (pas d'exception), et que plus tard vous détruisez l'objet (avec l'instruction "delete a;"), ce n'est pas cet opérateur qui sera appelé, mais l'opérateur delete habituel.

Aucun commentaire: