Article de Marco Cantù
La prochaine version 10.4 de RAD Studio comprend une nouvelle fonctionnalité intéressante pour le langage Delphi, la possibilité d'avoir des enregistrements avec des codes d'initialisation, de finalisation et d'affectation personnalisés.
Qu'est-ce qu'un enregistrement géré dans Delphi ?
Les enregistrements dans Delphi peuvent avoir des champs de n'importe quel type de données. Lorsqu'un enregistrement possède des champs simples (non gérés), comme des valeurs numériques ou d'autres valeurs énumérées, le compilateur n'a pas grand-chose à faire. La création et l'élimination de l'enregistrement consistent à allouer de la mémoire ou à se débarrasser de l'emplacement mémoire. (Notez que, par défaut, Delphi n'initialise pas les enregistrements).
Si un enregistrement possède un champ d'un type géré par le compilateur (comme une chaîne de caractères ou une interface), le compilateur doit injecter du code supplémentaire pour gérer l'initialisation ou la finalisation. Une chaîne de caractères, par exemple, est comptée par référence, de sorte que lorsque l'enregistrement sort du champ d'application, la chaîne de caractères contenue dans l'enregistrement doit voir son nombre de références diminuer, ce qui peut entraîner la désallocation de la mémoire pour la chaîne de caractères. Par conséquent, lorsque vous utilisez un tel enregistrement géré dans une section du code, le compilateur ajoute automatiquement un bloc try-finally autour de ce code et s'assure que les données sont effacées même en cas d'exception. C'est le cas depuis longtemps. En d'autres termes, les enregistrements gérés font partie du langage Delphi.
Enregistrements avec les opérateurs Initialize et Finalize
Dans la version 10.4, le type d'enregistrement Delphi prend désormais en charge l'initialisation et la finalisation personnalisées, au-delà des opérations par défaut effectuées par le compilateur pour les enregistrements gérés. Vous pouvez déclarer un enregistrement avec un code d'initialisation et de finalisation personnalisé, quel que soit le type de données de ses champs, et vous pouvez écrire ce code d'initialisation et de finalisation personnalisé. Pour ce faire, de nouveaux opérateurs spécifiques sont ajoutés au type d'enregistrement (vous pouvez avoir l'un sans l'autre si vous le souhaitez).
Vous trouverez ci-dessous un extrait de code simple :
type
TMyRecord = enregistrement
Valeur : Integer ;
classe operator Initialize (out Dest : TMyRecord) ;
classe operator Finalize(var Dest : TMyRecord) ;
fin ;
Vous devez bien sûr écrire le code pour les deux méthodes de la classe, par exemple en enregistrant leur exécution ou en initialisant la valeur de l'enregistrement - ici, nous enregistrons également une référence à l'emplacement de la mémoire, afin de voir quel enregistrement effectue chaque opération individuelle :
classe operator TMyRecord.Initialize (out Dest : TMyRecord) ;
begin
Dest.Value := 10 ;
Log('created' + IntToHex (Integer(Pointer(@Dest))))) ;
end ;
class operator TMyRecord.Finalize(var Dest : TMyRecord) ;
begin
Log('destroyed' + IntToHex (Integer(Pointer(@Dest))))) ;
end ;
La grande différence entre ce mécanisme de construction et ce qui était disponible auparavant pour les enregistrements est l'invocation automatique. Si vous écrivez quelque chose comme le code ci-dessous, vous pouvez invoquer à la fois l'initialisateur et le finalisateur, et vous retrouver avec un bloc try-finally généré par le compilateur pour votre instance d'enregistrement géré.
procédure LocalVarTest ; var my1 : TMyRecord ; begin Log (my1.Value.ToString) ; end ;
Avec ce code, vous obtiendrez un journal tel que :
créé 0019F2A8 10 détruit 0019F2A8
Un autre scénario est l'utilisation de variables en ligne, comme dans :
commencer var t : TMyRecord ; Log(t.Value.ToString) ;
ce qui permet d'obtenir la même séquence dans le journal.
L'opérateur d'affectation
L'affectation := copie purement et simplement toutes les données des champs de l'enregistrement. Bien qu'il s'agisse d'une valeur par défaut raisonnable, lorsque vous disposez de champs de données personnalisés et d'une initialisation personnalisée, vous pouvez souhaiter modifier ce comportement. C'est pourquoi vous pouvez également définir un opérateur d'affectation pour les enregistrements gérés de manière personnalisée. Le nouvel opérateur est invoqué avec la syntaxe :=, mais il est défini comme Assign :
class operator Assign (var Dest : TMyRecord ; const [ref] Src : TMyRecord) ;
La définition de l'opérateur doit suivre des règles très précises, notamment que le premier paramètre soit un paramètre de référence et que le second soit une constante passée par référence. Si vous ne le faites pas, le compilateur émet des messages d'erreur tels que les suivants :
[E2617 Le premier paramètre de l'opérateur Assign doit être un paramètre var du type de conteneur. [H2618 Le deuxième paramètre de l'opérateur Assign doit être un paramètre const[Ref] ou var du type de conteneur.
Il existe un exemple de cas invoquant l'opérateur Assign :
var my1, my2 : TMyRecord ; begin my1.Value := 22 ; my2 := my1 ;
produit ce journal (dans lequel nous ajoutons également un numéro de séquence à l'enregistrement) :
créé 5 0019F2A0 créé 6 0019F298 5 copié sur 6 détruit 6 0019F298 détruit 50019F2A0
Remarquez que la séquence de destruction est inversée par rapport à la séquence de construction.
Passage d'enregistrements gérés en tant que paramètres
Les enregistrements gérés peuvent fonctionner différemment des enregistrements ordinaires, y compris lorsqu'ils sont transmis en tant que paramètres ou renvoyés par une fonction. Voici plusieurs routines illustrant les différents scénarios :
procédure ParByValue (rec : TMyRecord) ; procédure ParByConstValue (const rec : TMyRecord) ; procédure ParByRef (var rec : TMyRecord) ; procedure ParByConstRef (const [ref] rec : TMyRecord) ; fonction ParReturned : TMyRecord ;
Sans passer en revue chaque registre un par un, voici le résumé de l'information :
- ParByValue crée un nouvel enregistrement et appelle l'opérateur d'affectation (s'il existe) pour copier les données, détruisant la copie temporaire à la sortie de la procédure.
- ParByConstValue ne fait pas de copie, et ne fait pas d'appel du tout
- ParByRef ne fait pas de copie, pas d'appel
- ParByConstRef ne fait pas de copie, pas d'appel
- ParReturned crée un nouvel enregistrement (via Initialize) et, au retour, appelle l'opérateur Assign, si l'appel est comme le suivant, et supprime l'enregistrement temporaire une fois qu'il a été réaffecté comme dans : my1 := ParReturned ;
Exceptions et dossiers gérés
Lorsqu'une exception est soulevée, les enregistrements sont généralement effacés, même en l'absence de bloc try, finally explicite, contrairement aux objets. Il s'agit là d'une différence fondamentale et de la clé de l'utilité réelle des enregistrements gérés.
procédure ExceptionTest ;
begin
var a : TMRE ;
var b : TMRE ;
raise Exception.Create('Message d'erreur') ;
fin ;
Dans cette procédure, il y a deux appels de constructeur et deux appels de destructeur. Là encore, il s'agit d'une différence fondamentale et d'une caractéristique essentielle des enregistrements gérés. Voir la section suivante sur un pointeur intelligent simple basé sur des enregistrements gérés.
Tableaux d'enregistrements gérés
Si vous définissez un tableau statique d'enregistrements gérés, ils sont initialisés en appelant l'opérateur Initialize à la déclaration du point :
var
a1 : array [1..5] of TMyRecord ; // appeler ici
begin
Log ('ArrOfRec') ;
Ils sont tous détruits lorsqu'ils sortent du champ d'application. Si vous définissez un tableau dynamique d'enregistrements gérés, le code d'initialisation est appelé lorsque le tableau est dimensionné (avec SetLength) :
var
a2 : tableau de TMyRecord ;
begin
Log ('ArrOfDyn') ;
SetLength(a2, 5) ; // appel ici
Conclusion
Il s'agit d'une brève présentation d'une nouvelle fonctionnalité très intéressante que Embarcadero ajoute au langage Delphi pour la prochaine version 10.4. Les enregistrements gérés fonctionnent pour les enregistrements génériques, par exemple, et dans de nombreux autres scénarios qui ne sont pas abordés ici. Bien qu'il s'agisse de la principale nouvelle fonctionnalité du langage, d'autres sont à venir, comme la nouvelle gestion unifiée de la mémoire sur toutes les plates-formes. Restez à l'écoute !
Si vous avez souscrit à un abonnement de mise à jour, l'un des avantages est l'accès aux versions bêta des prochaines versions. Il est encore temps de rejoindre notre programme bêta pour la version 10.4 !
Il s'agit d'un aperçu d'une version à venir de RAD Studio. Il peut toujours y avoir des bogues ou des changements de dernière minute. Rien n'est définitif tant que la version n'est pas officiellement disponible.

