III. Construction, destruction recopie et initialisation d'objets (suite).

 

  1. Objets membre :
  1. Introduction :

Il est tout à fait concevable qu'une données membre d'une classe, soit elle-même un objet d'une classe. Nous allons étudier le cas d'une classe point, contenant les coordonnées de ce point, et d'une classe cercle, comportant un objet de type point, qui est son centre, et un rayon de type integer :

class point
	{
	 int x,y;

    public:
	 int  init(int,int)
	 void affiche();
	};

class cercle
	{
	 point centre;
	 int rayon;

    public:
	 void affirayon();
	};

Si nous déclarons :

cercle c;

L'objet c possède un objet centre de type point, nous pouvons affiché le rayon par la méthode affirayon() :

c.affirayon;

mais nous ne pouvons utilisé la méthode init(int,int) de la classe point car centre est un objet privé, si il était public nous pourrions y accédé par la commande :

c.centre.init(4,6); //possible si centre est publique !!
c.init(4,5); //impossible !!

Nous pourrions aussi appelé la méthode init(int,int) a l'intérieur d'une méthode de la classe cercle, ainsi l'objet centre pourrai resté privé.

void init2(int a, int b)
	{
	 centre.init(a,b);
	}

Nous avons vu qu'une classe peut possédé un objet d'une autre classe, c'est une relation de "possession" : l'objet centre appartient à la classe cercle. Ceci est s'opposent à la notion d'héritage ou c'est une relation "d'être".

  1. Constructeur et destructeur pour objets membre :

Supposons maintenant que la classe point soit doté d'un constructeur :

class point
	{
	 int x,y;

    public:
	 point (int,int);
	};

La classe cercle ne peut plus etre définit sans constructeur, en effet si elle en était dépourvue, lors de la création d'un objet de type cercle, un objet de type point serait crée mais sont constructeur ne pourrait etre appelé faute d'argument. Il nous faut donc un constructeur pour la classe cercle qui puisse fournir au constructeur de la classe point les argument nécessaire à la création de l'objet centre.

Voici à quoi pourrai ressembler la définition de la classe cercle et de son constructeur :

class cercle
	{
	 point centre;
	 int rayon;

    public:
	 cercle (int, int, int);		
	};

cercle::cercle (int ray, int abs, int ord) :centre(abs,ord)
	{
	 rayon=ray;
	}

Le constructeur prend en argument la valeur du rayon du cercle, de l'abscisse et de l'ordonné du centre, qui seront transmit au constructeur de l'objet centre par :

:centre(abs, ord)

Ensuite vient la déclaration du constructeur de la classe cercle.

Les constructeurs seront appelés dans l'ordre suivant : point, cercle. Si il existe des destructeurs ils seront appelés dans l'ordre inverse des constructeurs.

La syntaxe que nous venons de décrire pour transmettre des arguments au constructeur d'un objet membre peut s'appliquer à n'importe qu'elle membre, même si il ne s'agit pas d'un objet :

class point
	{
	 int x,y;

     public:
	 point(abs,ord) :x(abs), y(ord) {}
	};

L'appel du constructeur de point provoquera l'initialisation de x et y; le corps de la fonction est maintenant vide.

  1. Constructeur par recopie :

Nous savons que pour chaque classe il existe un constructeur de recopie par défaut. Pour une classe contenant un objet comme données membre, c'est une recopie membre à membre qui s'effectue, c'est a dire que pour un objet membre c'est son propre constructeur de recopie qui est appelé(qu'il soit par défaut ou non).

Cela signifie qu'un constructeur de recopie par défaut sera satisfaisant si cette classe ne comporte pas d'objets ou de données membre dynamique, par contre si un objet membre contient en lui une partie dynamique, il devra etre munis du constructeur de recopie "profonde".

Si l'objet comporte des pointeurs, il faudra le munir d'un constructeur de recopie "profonde", qui devra prendre en charge l'intégralité de la recopie de l'objet (objets et données membre). Pour cela il devra transmettre au constructeur de recopie de ses objets membre les valeurs nécessaires à leur création, en utilisant la technique décrite dans le paragraphe 4.b.

 

  1. Tableau d'objets :

En C++ un tableau peut posséder des éléments de n'importe quels types, donc aussi des éléments de type classe. Ceci ne comporte aucune difficultés majeures en ce qui concerne la notation, par contre nous allons éclaircir certain point sur l'appel des constructeurs et aux initialiseurs.

  1. Notations :

Soit la traditionnelle classe point sans constructeur, définie par :

class point
	{
	 int x,y;

    public:
	 void init(int,int);
	 void affiche();
	};

Nous pouvons déclarer un tableau courbe, qui contient une vingtaine de point afin de représenté un graphique ou autre :

point courbe[20];

Si i est un entier la notation courbe[i] désigneras un objet de type point, et l'instruction :

courbe[i].affiche();

Déclenchera la fonction affcihe() pour l'objet courbe[i].

Attention : Un tableau d'objets n'est pas un objet. Dans l'esprit de la P.O.O. ce concept n'existe pas, par contre on défini une classe ayant comme données membre un tableau d'objets.

 

  1. Constructeur et initialiseurs :

Si la classe point possède un constructeur sans argument la déclaration :

point courbe[20];

Ne pose aucun problème, par contre si la classe contient un constructeur avec un argument ou plus, une telle déclaration posera problème a la compilation. En effet C++ n'est plus en mesure d'assuré l'appel du constructeur.

Il est cependant possible d'évité ce problème en ayant recoure à un initialiseur comportant une liste de valeur. Chaque valeur sera transmise au constructeur approprié. voici un petit exemple :

class point
	{
	 int x,y;

public:
	 point(int abs=0, int ord=0) :x(abs), y(ord){}
	 affiche();
	};

void main (void)
	{
	 point courbe[5]={2,4,48,4};
	}

S'il manque des valeurs c'est le constructeur sans argument qui est appelé(s'il existe).

  1. Cas des tableau dynamique d'objets :

Si l'on dispose d'une classe point, on peut créer dynamiquement un tableau d'objets à l'aide de l'opérateur new :

point* adrcourbe= new point[20];

Cette déclaration alloue l'espace mémoire pour vingt objets(consécutifs) de type point et place l'adresse du premier de ces objets dans adrcourbe.

Si aucun constructeur n'existe ou qu'il existe mais avec aucuns arguments alors cela ne pose aucun problème. Par contre s'il existe un constructeur avec arguments alors cette déclaration échoueras à la compilation. Ici il n'existe aucune liste de valeur que l'on puisse passer en argument au constructeur comme dans le paragraphe précédent.

Pour détruire notre tableau d'objet nous utiliserons l'instruction :

delete[] adrcourbe;

Noté la présence de [] qui précise que c'est un tableau d'objets. Lors du delete le destructeur sera appelé pour chacun des vingt objets.

  1. Les objets temporaires :

Lorsqu'une classe dispose d'un constructeur, on peut l'appelé explicitement. Supposons notre classe point possédant un constructeur à deux arguments. Si a est un objet de la classe point effectué l'affectation suivante :

a=point(5,6);

Dans une telle expression, l'évaluation de l'instruction point(5,6) conduit à :

Maintenant les objets temporaires ainsi crée ne possède plus aucun intérêt. Il pourra donc etre détruit à tout moment. Mais C++ ne précise pas à quel moment! Ceci peut poser problème pour des classes ou l'on tient compte du nombre d'objets au même moment.

Il existe plusieurs cas ou nous nous servons d'un objet temporaire :

fonct(point(1,5) );

return(point(1,3) );

Dans ces deux cas, un objet temporaire est crée, mais on ne sait quand il sera détruit.