• Aucun résultat trouvé

9.1 PRINCIPES DE GESTION DE L’ALLOCATION DYNAMIQUE EN C++

 Le langage C++ définit l'opérateur new de gestion de l'allocation dynamique qu'il est préférable d'utiliser à la place des fonctions correspondantes traditionnelles de la bibliothèque C (malloc, calloc, realloc, etc...).

 La fonction de la bibliothèque C de restitution de l’espace alloué free est réalisée par l'opérateur conjugué delete.

 Ces deux opérateurs sont appelés implicitement ou explicitement par les constructeurs et destructeurs.

9.2 L'OPÉRATEUR NEW

 L'opérateur new effectue l'allocation mémoire sans initialisation et appelle si nécessaire un constructeur. Il renvoie la constante NULL en cas d'échec.

 La définition d'un objet se distingue de la définition d'une instance qui provoque toujours l'appel d'un constructeur.

 La définition du constructeur par défaut est nécessaire pour initialiser chaque composante d'un tableau d'instances.

 La définition de la taille du tableau n'est pas obligatoire pour l'utilisation standard mais peut le devenir pour une surcharge.

Synopsis

new déclaration_de_type; // un objet

new classe; // une instance de la classe

new classe[Taille_du_Tableau]; // un tableau d'instances d'une classe

new classe(valeur); // initialisation d'une instance par transtypage fonctionnel

9.3 L'OPÉRATEUR DELETE

L'opérateur delete libère l'espace mémoire alloué par l'opérateur new selon la syntaxe : delete 0; // autorisé et sans effet.

delete P // libération de l'espace alloué à l'instance pointée par P

delete [] P // libération de l'espace alloué pour le tableau d'instances pointé par P

9.4 RÈGLES D'UTILISATION

 Les opérateurs new et delete d'allocation et de désallocation de la mémoire doivent être utilisés de préférence aux fonctions malloc et free car ils garantissent un meilleur contrôle des types ainsi qu'une une initialisation correcte de ceux qui sont définis par l'utilisateur.

 Il ne faut pas mélanger les fonctions et opérateurs d'allocation mémoire des langages C et C++, la gestion de la mémoire étant différente.

 Les opérateurs delete et delete[] ne doivent pas :

 générer d'erreur lorsqu'on leur transmet en argument un pointeur nul,

 être utilisés sur un pointeur de type void.

 Il est essentiel d'utiliser respectivement l'opérateur delete avec les pointeurs retournés par l'opérateur new et l'opérateur delete[] avec ceux qui sont retournés par l'opérateur new[].

 L'opérateur new[] alloue la mémoire et crée les objets dans l'ordre croissant des adresses. Inversement, l'opérateur delete[] détruit les objets du tableau dans un ordre décroissant des adresses.

9.5 EXEMPLES

Exemple 1

Soit le constructeur de la classe entier qui initialise tout entier à 0. Définir a) un pointeur initialisé sur un entier (int) indéfini,

b) un pointeur initialisé sur un entier (int) défini,

c) un pointeur sur un tabeau de la classe entier initialisé à 0, d) un pointeur sur un tableau d’entier non initialisé.

#include <iostream.h>

#define Taille 10 class entier { public:

int nombre ; int *ptnombre;

entier(){nombre=0; ptnombre=(int *) NULL; } // constructeur par défaut

~entier(){cout << "destructeur" << endl; delete [] ptnombre ; } };

int main()

{int *pi = new int; // initialisé mais indéfini cout << "*pi=" << *pi << endl ;

delete pi;

int a = 25;

int *pl = &a;

cout << "*pl=" << *pl << endl ;

int *pj = new int(543); // initialisation d'un pointeur sur la constante entière 543 cout << "*pj=" << *pj << endl ;

delete pj;

int *tableau = new int[Taille]; //appel du constructeur implicite et valeurs résiduelles int i;

cout << "tableau :" << endl;

for(i=0;i<Taille;i++) cout << tableau[i] << " ";

cout << endl;

delete [] tableau;

entier *ptab= new entier [Taille]; // appel du constructeur explicite cout << "tableau ptab : constructeur explicite" << endl;

for(i=0; i<Taille; i++) cout << (*ptab++).nombre << " ";

entier Tab[Taille]; // appel du constructeur explicite cout << "\ntableau Tab : constructeur explicite" << endl;

for(i=0; i<Taille; i++) cout << Tab[i].nombre << " "; cout << endl ; cout << "tableau pk : constructeur int implicite" << endl;

int *pk = new int [Taille] ; // création d'un tableau d'entiers non initialisés for(i=0; i< Taille;i++) cout << *pk++ << " "; cout <<endl ;

}

// résultat

*pi=0

*pl=25

*pj=543 tableau :

543 0 0 3369 0 0 0 0 0 0 // valeurs résiduelles tableau ptab : constructeur explicite

0 0 0 0 0 0 0 0 0 0

tableau Tab : constructeur explicite 0 0 0 0 0 0 0 0 0 0

tableau pk : constructeur int implicite 0 0 0 0 0 0 0 0 0 0

destructeur destructeur destructeur destructeur destructeur destructeur destructeur destructeur destructeur destructeur

Exemple 2

1°) Définir un constructeur explicite des données membres de la classe Produit.

2°) Utiliser le constructeur par défaut implicite. Conclusion.

// Classe Produit : allocation dynamique pour la donnée membre Nom avec l’opérateur new

#include <iostream.h>

#include <string.h>

#include <stdlib.h> // pour exit() class Produit

{char * nom; float prix;

public:

Produit (const char * Nom, float Valeur) // constructeur {nom = new char[strlen(Nom)+1];

if (nom == NULL) {cerr << "allocation impossible" << endl ; exit (1);}

strcpy (nom, Nom);

prix = Valeur;

}

void AfficheToi() const

{cout << "Produit " << nom << " de prix " << prix << endl ;}

};

int main()

{ Produit P1("SAVON",7.5);

Produit * Ptr = &P1;

Ptr->AfficheToi();

Ptr = new Produit("FARINE 1KG", 15.5);

Ptr->AfficheToi();

// L'instruction :

// Produit * Ptr2 = new Produit[100];

// provoque l'erreur de compilation :

// Cannot find default constructor to initialize array element of type 'Produit' return 1;

}

// résultat

Produit SAVON de prix 7.5

Produit FARINE 1KG de prix 15.5

Dans l'exemple ci-après, la chaîne de caractères doit être initialisée avec un caractère au moins.

Exemple 3

// Classe Produit : version avec les opérateurs new et delete

#include <iostream.h>

#include <string.h>

#include <stdlib.h>

class Produit {private:

char * nom;

float prix;

char * alloue(int LgrMem)

{// fonction membre privée d'allocation pour la donnée membre nom char * Ptr = new char[LgrMem];

if (Ptr == NULL) {cerr << "plus de place en mémoire" << endl ; exit (1); } return Ptr;

} public:

Produit( const char * Nom, float Valeur) // un constructeur {nom = alloue(strlen(Nom)+1);

strcpy (nom, Nom);

prix = Valeur;

}

Produit() // le constructeur par défaut

{nom = alloue(1); nom[0] = '\0';

prix = 0;

cout << "constructeur par défaut" << endl ; }

void AfficheToi() const

{cout << "Produit " << nom << " de prix " << prix << endl ; } };

int main()

{ Produit P1("SAVON",7.5); // appel du constructeur P1.AfficheToi();

Produit *Ptr = new Produit[2]; // 2 instances et appel du constructeur par défaut for (int k=0; k<2; k++) Ptr[k].AfficheToi();

Produit TabProd[2]; // Idem cas précédent }

// résultat

Produit SAVON de prix 7.5 constructeur par défaut constructeur par défaut Produit de prix 0 Produit de prix 0 constructeur par défaut constructeur par défaut

Remarque

Le traitement commun exécuté par les deux constructeurs est implémenté sous la forme d'une fonction membre privée, appelable par chacun.