![]() |
![]() |
![]() |
|
![]() |
|
15 Fév 2010
Windows Seven : Les premières conclusions
06 Fév 2010
Une semaine d'actu : retour sur l'actualité de la semaine
28 Déc 2009
2000-2010 : Les révolutions qui ont changé le monde
31 Oct 2009
Google Maps Navigation : nouvelle bombe atomique parmi les GPS
24 Juil 2009
HADOPI 2 : Ce qu'on en pense à l'étranger
Google : le maître des noms de domaines
La nouvelle version de GMail en images !
Barème de rémunération pour les disques durs multimédias
TRichEdit : scroll par la molette de la souris
Récupérateur de mots de passe WLM
+4 visiteurs en ligne
Vous êtes ici : Accueil › Delphi › Tutoriaux › Une fonction créant un objet: la bonne façon de procéder
Auteur : Flo
Catégorie : Manipulation de fichiers
Niveau : Intermédiaire
Déposé 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) :
function CreateBmp(W, H: Integer): TBitmap; begin Result := TBitmap.Create; Result.Width := W; Result.Height := H; 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.
procedure CreerBitmap(W, H: Integer; out Bmp: TBitmap); begin Bmp := TBitmap.Create; Bmp.Width := W; Bmp.Height := H; 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 :
var B: TBitmap; begin B := CreerBitmap(100, 200); B.SaveToFile('C:\xxx.bmp'); B.Free; // Libération de l'objet qui a été créé dans la fonction ! end;
et ainsi :
var B: TBitmap; begin CreerBitmap(100, 200, B); B.SaveToFile('C:\xxx.bmp'); B.Free; // Libération de l'objet qui a été créé dans la procédure ! 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)
TBitmapCreator = class(TObject) private FList: TObjectList; public constructor Create; destructor Destroy; override; function CreerBitmap(W, H: Integer): TBitmap; end; var BmpCreator: TBitmapCreator; [ ... ] constructor TBitmapCreator.Create; begin inherited; FList := TObjectList.Create(True); end; destructor TBitmapCreator.Destroy; begin FList.Free; inherited; end; function TBitmapCreator.CreerBitmap(W, H: Integer): TBitmap; begin Result := TBitmap.Create; Result.Width := W; Result.Height := H; FList.Add(Result); 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 :
Classe
Instance (de classe)= objet
Référence vers l'objet => pointeur
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.
var P1, P2: Integer; begin New(P1); New(P2); P1^ := 10; P2^ := 20; {>> Assigne la valeur de P2 à P1 => Deux espaces mémoire distincts possède la valeur de P1^ } P1^:= P2^; P1^ := 10; P2^ := 20; {>> Affecte l'addresse mémoire de P2 à P1 } P1 := P2; { Maintenant, deux choses arrivent : - Si on modifie P1^ ou P2^, l'autre est touché par la modification - On perd la référence de P1 et hop ! Fuite de mémoire ! Il aurait fallu faire Dispose(P1) avant cette ligne. } 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
Alignement de séquences d'ADN
Notification des modifications des fichiers d'un dossier
EXSTREAM, une unité qui simplifie la création et la lecture de flux (fichiers,...)
Soyez le premier à commenter cette ressource !
Ajoutez votre commentaire & avis sur la ressource :
Vous n'êtes pas connecté !
Rejoignez dès maintenant la communauté en 3 clics et exprimez votre avis !
J'ai déjà un compte