Free Tools

Votre publicité ici ?


Top des logiciels

1. HxD Hex Editor 1.6.1 (322 fois)

2. Open Office 2.2.1 (275 fois)

3. Delphi 6 (244 fois)

4. Windows Live Messenger (171 fois)

5. FireFox 2.0.0.12 (130 fois)


En bref

18 Août 2008 Windows 7 : tout savoir sur le prochain système de Microsoft

13 Août 2008 The Pirate Bay censuré en Italie - L'arrêt de mort du réseau P2P ?

11 Août 2008 Hop : le nouveau téléphone jetable à 15€

04 Août 2008 OVH : la lutte contre le P2P commence

28 Juil 2008 Cuil : le futur concurrent de Google ?

Consulter les archives


Les derniers dossiers


Les ressources Delphi et C/C++

Music Pro Package

Afficher la date et heure du jour

Fonction affine

Alignement de séquences d'ADN

TatNum : gestion des images avec Qt


Membres en ligne :


Nos partenaires

Espacerezo

KilaSoft


À votre tour, devenez partenaire de mx-dev.net.



Logo mx-dev.net

Le 1er du Web - Voter pour mx-dev.net


Vous êtes ici : AccueilDelphiTutoriaux › Une fonction créant un objet: la bonne façon de procéder


Une fonction créant un objet: la bonne façon de procéder


Informations sur le tutorial :

Auteur : Flo

Catégorie : Manipulation de fichiers

Niveau : Intermédiaire

Posté le : 28 Août 2007 à 12h27


I.Introduction

Au fur et à mesure que je programme, et que j'apprends à programmer, les fonctions renvoyant des objets m'ont toujours questionné. En fait, ce n'est pas tant la création de l'objet qui pose problème, mais sa libération, ce qui en fait est bien plus grave.

 

II.Exemple concret et explications

Pour illustrer le phénomène, prenons le cas d'une fonction toute simple, qui crée un bitmap de dimensions (W, H) :

 

  1. function CreateBmp(W, H: Integer): TBitmap;
  2. begin
  3. Result := TBitmap.Create;
  4. Result.Width := W;
  5. Result.Height := H;
  6. end;

 

Cette fonction crée un bitmap, affecte sa REFERENCE à Result et définit ses propriétés Width et Height.
Au final, le pointeur renvoyé est l'instance de l'objet.

Pour avoir le même effet avec une procédure, il faut utiliser un paramètre « out » (et non pas « var ») Ceci permet à une procédure de renvoyer en quelque sorte, plusieurs valeurs, ce qu'une fonction ne peut pas faire.


Théoriquement parlant, le paramètre « var » suppose que la valeur donnée est valide, alors que le paramètre « out » stipule que la variable transmise n'est pas assignée (un pointeur sur nil par exemple).
En pratique, l'effet appliqué est le même (au niveau du code assembleur) à une différence près: le mot clé « out » oblige Delphi à faire la vérification.

Quand je lis des codes sources, je remarque qu'on substitue régulièrement

« out » au profit de « var » alors qu'il serait plus adapté.

Du coup, avec tout ça, c'est la procédure elle-même qui crée le bitmap.

 

  1. procedure CreerBitmap(W, H: Integer; out Bmp: TBitmap);
  2. begin
  3. Bmp := TBitmap.Create;
  4. Bmp.Width := W;
  5. Bmp.Height := H;
  6. end;

 

Ceci est la procédure strictement équivalente. Et je dis bien strictement, car c'est réellement la même chose, sauf à l'appel.

 

On utilisera ces méthodes comme cela :

 

  1. var
  2. B: TBitmap;
  3. begin
  4. B := CreerBitmap(100, 200);
  5. B.SaveToFile('C:\xxx.bmp');
  6. B.Free; // Libération de l'objet qui a été créé dans la fonction !
  7. end;

 

et ainsi :

 

  1. var
  2. B: TBitmap;
  3. begin
  4. CreerBitmap(100, 200, B);
  5. B.SaveToFile('C:\xxx.bmp');
  6. B.Free; // Libération de l'objet qui a été créé dans la procédure !
  7. end;

 

C'est identique, et pour dire simplement, kif-kif-pareil.

 

III.Remarque à propos d'un conseil de f0xi

Dans je ne sais plus trop quel thread du forum, f0xi affirmait qu'il valait mieux utiliser une procédure avec paramètre variable, plutôt que de coder une fonction.

Mais pas du tout ! C'est tout le contraire !

Par contre, et je rejoins f0xi la-dessus, on aurait plus tendance à oublier de libérer en utilisant une fonction plutôt qu'une procédure.

En effet, certains seraient amenés à écrire :

CreerBitmap(100, 200).SaveToFile('C:\xxx.bmp');


Dans ce cas, l'instance est perdue, et c'est au moins 10 octets de perdus ...
Alors qu'il est obligatoire de passer par une variable pour accéder au résultat avec la procédure. D'où peut être le fait qu'on pense plus à libérer.

Et je dis bien « on » car tout dépend entre quelles mains tombe ce code. Ceux qui aiment bien les côtés pratiques que peuvent avoir une fonction renvoyant un résultat (dans une conception un peu poussée avec des classes) penseront à libérer.


Les autres non.

D'où ce conseil de f0xi, à ne pas entièrement suivre à la lettre.

 

IV.Contre-exemple: la programmation autorise tout !

Attention: danger ! Ceux qui n'ont pas tout compris ce qui a été dit plus haut feraient mieux de ne pas lire la suite sous peine de ressortir encore plus embrouillé qu'au départ !

 

Prenons une classe, TBitmapCreator, dont le but est de délivrer des TBitmap et à garder leur référence en mémoire.

 

Voici le code : (les commentaires suivent)

 

  1. TBitmapCreator = class(TObject)
  2. private
  3. FList: TObjectList;
  4. public
  5. constructor Create;
  6. destructor Destroy; override;
  7. function CreerBitmap(W, H: Integer): TBitmap;
  8. end;
  9.  
  10. var
  11. BmpCreator: TBitmapCreator;
  12.  
  13. [ ... ]
  14.  
  15. constructor TBitmapCreator.Create;
  16. begin
  17. inherited;
  18. FList := TObjectList.Create(True);
  19. end;
  20.  
  21. destructor TBitmapCreator.Destroy;
  22. begin
  23. FList.Free;
  24. inherited;
  25. end;
  26.  
  27. function TBitmapCreator.CreerBitmap(W, H: Integer): TBitmap;
  28. begin
  29. Result := TBitmap.Create;
  30. Result.Width := W;
  31. Result.Height := H;
  32. FList.Add(Result);
  33. end;

 

Avec cette classe simpliste, il est tout à fait possible de faire :

BmpCreator.CreerBitmp(100, 200).SaveToFile('C:\xxx.bmp');

 

Car, et uniquement car, la classe TBitmapCreator détient la liste de tous les bitmap crées et qu'elle les libère à sa destruction (Flist.Free).

Ceci sous entend évidemment que BmpCreator soit bel et bien libéré à un moment où à un autre, mais là, on s'éloigne du sujet.

 

Comme quoi, il est possible de ne pas respecter la règle que je viens de vous dicter ! Attention cependant à ne pas tomber dans l'excès inverse: garder trop d'objets inutiles en vie consomme de la mémoire pour rien...

 

V.Rappel sur les classes, objets ... bref, le vocabulaire

Maintenant que vous avez compris le truc, toute votre vie va se simplifier.

 

Attention cependant de ne pas confondre :

 

Une classe peut avoir plusieurs instances (sauf si c'est un singleton mais bon ...).

 

Un objet peut aussi avoir plusieurs références, qui sont de pointeurs vers la zone mémoire où il est stocké. A la différence des autres types, il est impossible de manipuler directement l'objet, sans passer par un pointeur.
Quand un même objet a plusieurs références, c'est l'une d'entre elle (et seulement une, pas deux) qui se charge d'appeler Free() pour le libérer.

 

VI.Pointeurs: assignation ou affectation ?

Attention aussi à la nuance, subtile certes, entre affecter et assigner, sous peine de ne rien comprendre à tout le code qui précède.

  1. var
  2. P1, P2: Integer;
  3. begin
  4. New(P1);
  5. New(P2);
  6. P1^ := 10;
  7. P2^ := 20;
  8.  
  9. {>> Assigne la valeur de P2 à P1 => Deux espaces mémoire distincts possède la valeur de P1^ }
  10. P1^:= P2^;
  11.  
  12. P1^ := 10;
  13. P2^ := 20;
  14. {>> Affecte l'addresse mémoire de P2 à P1 }
  15. P1 := P2;
  16.  
  17. { Maintenant, deux choses arrivent :
  18. - Si on modifie P1^ ou P2^, l'autre est touché par la modification
  19. - On perd la référence de P1 et hop ! Fuite de mémoire ! Il aurait fallu faire Dispose(P1) avant cette ligne. }
  20. end;

 

PS: avec des variables "classiques": X1 := X2 correspond toujours à une assignation (sauf le type string, géré par Delphi de façon spéciale)

 

Avec des objets: X1 := X2 correspond toujours à une affectation. L'assignation, c'est avec TPersistent.Assign().
Sauf (je vais vous achever là ^^) si c'est une propriété d'un objet, genre TImage.Picture, auquel cas c'est le Setter qui détermine si on affecte ou assigne. C'est d'ailleurs pour cela qu'on peut faire Image1.Picture := nil, sans provoquer de fuite de mémoire car le TImage assigne la propriété Picture.

 

VII.Conclusion

Je m'arrête ici sur les rappels, car c'est sans fin.

J'espère juste vous avoir désembrouillé l'esprit, et que vous vous couchiez ce soir en vous disant: « bon, je digérerai ça demain »

 

VIII.Remerciements

A tous ceux qui m'ont permis d'apprendre ce que je viens de vous raconter, à Cirec pour m'avoir incité à écrire sur le sujet, et à Cari pour m'avoir incité (indirectement) à le transformer en tutorial et à vous, pour m'avoir lu jusqu'ici ! Enfin, mention spéciale à Forman pour ces éclaircissements