🔝 Retour au Sommaire
Les constructeurs et destructeurs sont des méthodes spéciales qui gèrent le cycle de vie d'un objet : sa création (naissance) et sa destruction (mort). Comprendre ces concepts est essentiel pour éviter les fuites mémoire et créer des applications robustes.
Un constructeur est une méthode spéciale qui initialise un nouvel objet. C'est la première méthode appelée lors de la création d'une instance d'une classe. Le constructeur prépare l'objet pour être utilisé.
Imaginez la construction d'une maison :
- Le constructeur est l'équipe de construction qui bâtit la maison
- Avant que vous puissiez habiter la maison, elle doit être construite
- Le constructeur s'assure que tout est en place : fondations, murs, toit, électricité
En Object Pascal, un constructeur se déclare avec le mot-clé constructor :
type
TPersonne = class
private
FNom: string;
FAge: Integer;
public
constructor Create; // Constructeur
property Nom: string read FNom write FNom;
property Age: Integer read FAge write FAge;
end;
// Implémentation
constructor TPersonne.Create;
begin
inherited Create; // Appelle le constructeur de la classe parent
FNom := '';
FAge := 0;
ShowMessage('Une personne a été créée');
end;var
Personne: TPersonne;
begin
// Appel du constructeur
Personne := TPersonne.Create;
try
Personne.Nom := 'Marie';
Personne.Age := 25;
ShowMessage(Personne.Nom);
finally
Personne.Free;
end;
end;Le mot-clé inherited dans un constructeur appelle le constructeur de la classe parent. C'est important car :
- Toutes les classes héritent de
TObject TObject.Createalloue la mémoire nécessaire pour l'objet- Sans cet appel, l'objet ne serait pas correctement initialisé
constructor TMaClasse.Create;
begin
inherited Create; // TOUJOURS appeler inherited dans un constructeur
// Votre code d'initialisation ici
end;Les constructeurs peuvent accepter des paramètres pour initialiser l'objet avec des valeurs spécifiques :
type
TCompte = class
private
FTitulaire: string;
FSolde: Double;
public
constructor Create(ATitulaire: string; ASoldeInitial: Double);
property Titulaire: string read FTitulaire;
property Solde: Double read FSolde write FSolde;
end;
constructor TCompte.Create(ATitulaire: string; ASoldeInitial: Double);
begin
inherited Create;
FTitulaire := ATitulaire;
FSolde := ASoldeInitial;
end;var
MonCompte: TCompte;
begin
// Création avec initialisation
MonCompte := TCompte.Create('Jean Dupont', 1000.0);
try
ShowMessage(Format('%s a un solde de %.2f €',
[MonCompte.Titulaire, MonCompte.Solde]));
finally
MonCompte.Free;
end;
end;Une classe peut avoir plusieurs constructeurs avec des paramètres différents. C'est ce qu'on appelle la surcharge (overload) :
type
TRectangle = class
private
FLargeur: Double;
FHauteur: Double;
public
constructor Create; overload; // Constructeur par défaut
constructor Create(ACote: Double); overload; // Carré
constructor Create(ALargeur, AHauteur: Double); overload; // Rectangle
property Largeur: Double read FLargeur write FLargeur;
property Hauteur: Double read FHauteur write FHauteur;
end;
// Constructeur par défaut : rectangle 1x1
constructor TRectangle.Create;
begin
inherited Create;
FLargeur := 1;
FHauteur := 1;
end;
// Constructeur pour un carré
constructor TRectangle.Create(ACote: Double);
begin
inherited Create;
FLargeur := ACote;
FHauteur := ACote;
end;
// Constructeur pour un rectangle quelconque
constructor TRectangle.Create(ALargeur, AHauteur: Double);
begin
inherited Create;
FLargeur := ALargeur;
FHauteur := AHauteur;
end;var
Rect1, Rect2, Rect3: TRectangle;
begin
// Utilisation du constructeur par défaut
Rect1 := TRectangle.Create;
// Utilisation du constructeur pour un carré
Rect2 := TRectangle.Create(5);
// Utilisation du constructeur pour un rectangle
Rect3 := TRectangle.Create(4, 7);
try
ShowMessage(Format('Rect1 : %.1f x %.1f', [Rect1.Largeur, Rect1.Hauteur]));
ShowMessage(Format('Rect2 : %.1f x %.1f', [Rect2.Largeur, Rect2.Hauteur]));
ShowMessage(Format('Rect3 : %.1f x %.1f', [Rect3.Largeur, Rect3.Hauteur]));
finally
Rect1.Free;
Rect2.Free;
Rect3.Free;
end;
end;Un destructeur est une méthode spéciale qui nettoie et libère les ressources utilisées par un objet avant sa destruction. C'est la dernière méthode appelée avant que l'objet ne soit supprimé de la mémoire.
Continuons avec l'analogie de la maison :
- Le destructeur est l'équipe de démolition
- Avant de détruire la maison, il faut couper l'eau, l'électricité, le gaz
- Il faut aussi récupérer les objets de valeur
- Enfin, on peut démolir la structure
En Object Pascal, un destructeur se déclare avec le mot-clé destructor. Par convention, il s'appelle Destroy :
type
TFichier = class
private
FNomFichier: string;
FContenu: TStringList;
public
constructor Create(ANomFichier: string);
destructor Destroy; override; // Destructeur
property NomFichier: string read FNomFichier;
end;
constructor TFichier.Create(ANomFichier: string);
begin
inherited Create;
FNomFichier := ANomFichier;
FContenu := TStringList.Create; // Création d'un objet interne
ShowMessage('Fichier ouvert : ' + FNomFichier);
end;
destructor TFichier.Destroy;
begin
FContenu.Free; // Libérer l'objet interne
ShowMessage('Fichier fermé : ' + FNomFichier);
inherited Destroy; // TOUJOURS appeler inherited en dernier
end;- Toujours nommé
Destroy: par convention - Mot-clé
override: car on redéfinit le destructeur deTObject inherited Destroyen dernier : contrairement au constructeur oùinheritedest appelé en premier- Libérer les objets internes : tout objet créé dans le constructeur doit être libéré dans le destructeur
Au lieu d'appeler directement Destroy, on utilise la méthode Free qui :
- Vérifie si l'objet existe (n'est pas
nil) - Appelle
Destroysi l'objet existe
Note : Free ne met pas la variable à nil. Pour cela, utilisez FreeAndNil (voir plus loin).
var
MonFichier: TFichier;
begin
MonFichier := TFichier.Create('document.txt');
try
// Utilisation de l'objet
finally
MonFichier.Free; // Appelle Destroy de manière sécurisée
end;
end;Voici un exemple illustrant l'importance des constructeurs et destructeurs :
type
TConnexionDB = class
private
FServeur: string;
FBaseDeDonnees: string;
FConnecte: Boolean;
FDateConnexion: TDateTime;
public
constructor Create(AServeur, ABaseDeDonnees: string);
destructor Destroy; override;
procedure Connecter;
procedure Deconnecter;
property Connecte: Boolean read FConnecte;
property DateConnexion: TDateTime read FDateConnexion;
end;
constructor TConnexionDB.Create(AServeur, ABaseDeDonnees: string);
begin
inherited Create;
FServeur := AServeur;
FBaseDeDonnees := ABaseDeDonnees;
FConnecte := False;
FDateConnexion := 0;
ShowMessage('Objet connexion créé');
end;
destructor TConnexionDB.Destroy;
begin
// Nettoyer avant de détruire
if FConnecte then
Deconnecter;
ShowMessage('Objet connexion détruit');
inherited Destroy; // Toujours en dernier
end;
procedure TConnexionDB.Connecter;
begin
if not FConnecte then
begin
// Simulation de connexion
FConnecte := True;
FDateConnexion := Now;
ShowMessage(Format('Connecté à %s/%s', [FServeur, FBaseDeDonnees]));
end;
end;
procedure TConnexionDB.Deconnecter;
begin
if FConnecte then
begin
// Simulation de déconnexion
FConnecte := False;
ShowMessage('Déconnecté');
end;
end;var
Connexion: TConnexionDB;
begin
Connexion := TConnexionDB.Create('localhost', 'MaBase');
try
Connexion.Connecter;
// Utilisation de la connexion
ShowMessage('Travail avec la base de données...');
finally
Connexion.Free; // Le destructeur déconnectera automatiquement
end;
end;Lorsqu'on travaille avec l'héritage, l'ordre d'appel est important :
type
// Classe de base
TVehicule = class
private
FMarque: string;
public
constructor Create(AMarque: string);
destructor Destroy; override;
end;
// Classe dérivée
TVoiture = class(TVehicule)
private
FNombrePortes: Integer;
public
constructor Create(AMarque: string; ANombrePortes: Integer);
destructor Destroy; override;
end;
// Implémentation TVehicule
constructor TVehicule.Create(AMarque: string);
begin
inherited Create;
FMarque := AMarque;
ShowMessage('Véhicule créé : ' + FMarque);
end;
destructor TVehicule.Destroy;
begin
ShowMessage('Véhicule détruit : ' + FMarque);
inherited Destroy;
end;
// Implémentation TVoiture
constructor TVoiture.Create(AMarque: string; ANombrePortes: Integer);
begin
inherited Create(AMarque); // Appelle le constructeur du parent
FNombrePortes := ANombrePortes;
ShowMessage(Format('Voiture avec %d portes créée', [FNombrePortes]));
end;
destructor TVoiture.Destroy;
begin
ShowMessage('Voiture détruite');
inherited Destroy; // Appelle le destructeur du parent
end;var
MaVoiture: TVoiture;
begin
MaVoiture := TVoiture.Create('Renault', 5);
// Ordre de création :
// 1. TObject.Create (implicite)
// 2. TVehicule.Create
// 3. TVoiture.Create
try
// Utilisation
finally
MaVoiture.Free;
// Ordre de destruction :
// 1. TVoiture.Destroy
// 2. TVehicule.Destroy
// 3. TObject.Destroy (implicite)
end;
end;Si une erreur se produit dans un constructeur, l'objet peut être partiellement construit. Il faut gérer ce cas :
type
TRessource = class
private
FDonnees: TStringList;
FFichier: TFileStream;
public
constructor Create(ANomFichier: string);
destructor Destroy; override;
end;
constructor TRessource.Create(ANomFichier: string);
begin
inherited Create;
FDonnees := TStringList.Create;
try
FFichier := TFileStream.Create(ANomFichier, fmOpenRead);
except
// Si l'ouverture du fichier échoue, libérer ce qui a été créé
FDonnees.Free;
raise; // Relancer l'exception
end;
end;
destructor TRessource.Destroy;
begin
FFichier.Free;
FDonnees.Free;
inherited Destroy;
end;Voici un exemple montrant l'utilisation de constructeurs/destructeurs pour gérer un compteur d'instances :
type
TConnexion = class
private
class var FNombreInstances: Integer; // Variable de classe
FID: Integer;
public
constructor Create;
destructor Destroy; override;
class function ObtenirNombreInstances: Integer;
property ID: Integer read FID;
end;
constructor TConnexion.Create;
begin
inherited Create;
Inc(FNombreInstances);
FID := FNombreInstances;
ShowMessage(Format('Connexion #%d créée (Total: %d)',
[FID, FNombreInstances]));
end;
destructor TConnexion.Destroy;
begin
ShowMessage(Format('Connexion #%d détruite (Restant: %d)',
[FID, FNombreInstances - 1]));
Dec(FNombreInstances);
inherited Destroy;
end;
class function TConnexion.ObtenirNombreInstances: Integer;
begin
Result := FNombreInstances;
end;var
Conn1, Conn2, Conn3: TConnexion;
begin
Conn1 := TConnexion.Create; // Connexion #1 créée (Total: 1)
Conn2 := TConnexion.Create; // Connexion #2 créée (Total: 2)
Conn3 := TConnexion.Create; // Connexion #3 créée (Total: 3)
ShowMessage('Instances actives : ' +
IntToStr(TConnexion.ObtenirNombreInstances));
Conn2.Free; // Connexion #2 détruite (Restant: 2)
ShowMessage('Instances actives : ' +
IntToStr(TConnexion.ObtenirNombreInstances));
Conn1.Free;
Conn3.Free;
end;Pour éviter les erreurs avec des pointeurs dangling (pointant vers de la mémoire libérée), utilisez FreeAndNil :
var
MonObjet: TMonObjet;
begin
MonObjet := TMonObjet.Create;
try
// Utilisation
finally
FreeAndNil(MonObjet); // Libère ET met à nil
end;
// MonObjet vaut maintenant nil, pas de risque d'accès invalide
end;FreeAndNil fait deux choses :
- Appelle
Freesur l'objet - Met la variable à
nil
Les constructeurs peuvent être virtuels, ce qui permet le polymorphisme :
type
TDocument = class
public
constructor Create; virtual;
procedure Sauvegarder; virtual; abstract;
end;
TDocumentTexte = class(TDocument)
public
constructor Create; override;
procedure Sauvegarder; override;
end;
TDocumentImage = class(TDocument)
public
constructor Create; override;
procedure Sauvegarder; override;
end;
constructor TDocument.Create;
begin
inherited Create;
ShowMessage('Document générique créé');
end;
constructor TDocumentTexte.Create;
begin
inherited Create;
ShowMessage('Document texte créé');
end;
constructor TDocumentImage.Create;
begin
inherited Create;
ShowMessage('Document image créé');
end;type
TDocumentClass = class of TDocument;
procedure CreerEtUtiliser(ClasseDocument: TDocumentClass);
var
Doc: TDocument;
begin
Doc := ClasseDocument.Create; // Polymorphisme au niveau de la classe
try
Doc.Sauvegarder;
finally
Doc.Free;
end;
end;
begin
CreerEtUtiliser(TDocumentTexte);
CreerEtUtiliser(TDocumentImage);
end;// ✅ Bon
var
Obj: TMonObjet;
begin
Obj := TMonObjet.Create;
try
// Utilisation
finally
Obj.Free;
end;
end;
// ❌ Mauvais - risque de fuite mémoire en cas d'exception
var
Obj: TMonObjet;
begin
Obj := TMonObjet.Create;
// Utilisation
Obj.Free;
end;constructor TPersonne.Create;
begin
inherited Create;
FNom := ''; // Toujours initialiser
FAge := 0; // Ne pas laisser de valeurs indéfinies
FActif := False;
FDateCreation := Now;
end;constructor TMaClasse.Create;
begin
inherited Create;
FListe := TStringList.Create;
FDictionnaire := TDictionary<string, Integer>.Create;
end;
destructor TMaClasse.Destroy;
begin
FDictionnaire.Free; // Libérer tout ce qui a été créé
FListe.Free;
inherited Destroy; // Toujours en dernier
end;// ✅ Bon
constructor TMaClasse.Create;
begin
inherited Create; // EN PREMIER
// Initialisation
end;
destructor TMaClasse.Destroy;
begin
// Nettoyage
inherited Destroy; // EN DERNIER
end;procedure TForm.FermerConnexion;
begin
FreeAndNil(FConnexion); // Libère et met à nil
// FConnexion vaut maintenant nil, sûr pour les tests ultérieurs
end;constructor TRectangle.Create(ALargeur, AHauteur: Double);
begin
inherited Create;
if (ALargeur <= 0) or (AHauteur <= 0) then
raise Exception.Create('Les dimensions doivent être positives');
FLargeur := ALargeur;
FHauteur := AHauteur;
end;// ❌ Mauvais
constructor TMaClasse.Create;
begin
// Manque inherited Create !
FValeur := 10;
end;
// ✅ Bon
constructor TMaClasse.Create;
begin
inherited Create;
FValeur := 10;
end;// ❌ Mauvais - fuite mémoire !
constructor TMaClasse.Create;
begin
inherited Create;
FListe := TStringList.Create;
end;
destructor TMaClasse.Destroy;
begin
// Manque FListe.Free !
inherited Destroy;
end;
// ✅ Bon
destructor TMaClasse.Destroy;
begin
FListe.Free;
inherited Destroy;
end;// ❌ Mauvais
MyObject.Destroy; // Ne faites jamais ça !
// ✅ Bon
MyObject.Free; // Utilisez toujours Free-
Constructeur (
constructor Create)- Initialise un nouvel objet
- Appelé avec le mot-clé
Create inherited Createdoit être appelé en premier- Peut avoir plusieurs versions (surcharge avec
overload) - Peut accepter des paramètres pour l'initialisation
-
Destructeur (
destructor Destroy)- Nettoie et libère les ressources
- Toujours nommé
Destroy inherited Destroydoit être appelé en dernier- Libère tous les objets internes créés
- Ne jamais appeler directement, utiliser
Free
-
Bonnes pratiques
- Toujours utiliser
try...finallyavecFree - Initialiser tous les champs dans le constructeur
- Libérer tous les objets internes dans le destructeur
- Utiliser
FreeAndNilpour éviter les pointeurs dangling - Valider les paramètres du constructeur
- Toujours utiliser
-
Gestion mémoire
- Les objets Delphi ne sont pas libérés automatiquement
- Toujours appeler
Freepour éviter les fuites mémoire - Le bloc
try...finallygarantit la libération même en cas d'exception
Maîtriser les constructeurs et destructeurs est essentiel pour créer des applications Delphi robustes et éviter les fuites mémoire.
⏭️ Interfaces