Destruction liste chaînée

Résolu
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016 - 8 janv. 2008 à 12:11
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016 - 10 janv. 2008 à 14:59
Bonjour à tous,

J'ai modélisé une classe (TA par exemple) qui contient l'adresse d'une instance d'une autre classe (que l'on nommera TB). Ensuite, chaque instance de la classe TB contient l'adresse de l'instance de TB suivante, de manière à constituer une liste chaînée simple.

Mon souci intervient dans le destructeur de la classe TA, quand je cherche à détruire toutes les instances de TB, en commençant bien évidemment par le dernier, pour ne pas rompre la liste.

Voici un petit schéma ASCII illustrant la modélistation, et le code du destructeur de la classe TA que j'ai écrit.

  ____________
 |   A1: TA   |
 |------------|
 | ...        |        ____________
 | FirstB: TB +------>|   B1: TB   |
 |------------|       |------------|
 | Create();  |       | ...        |        ____________
 | Destroy(); |       | NextB: TB  +------>|   B2: TB   |
 | ...        |       |------------|       |------------|
 |____________|       | Create();  |       | ...        |
                      | Destroy(); |       | NextB: TB  +------> nil
                      | ...        |       |------------|
                      |____________|       | Create();  |
                                           | Destroy(); |
                                           | ...        |
                                           |____________|

destructor TA.Destroy();
var
  CurrentB: TB;
begin
  if FirstB<>nil then // vérification au cas où liste chaînée vide
  begin
    CurrentB := FirstB; // on pointe sur la première instance de la liste
    while CurrentB.NextB<>nil do // tant qu'un élément suit
    begin
      while CurrentB.NextB<>nil do // recherche de la dernière instace de TB constituant la liste chaînée
        CurrentB := CurrentB.NextB;
      CurrentB.Free; // appelle le destructeur de TB pour détruire le dernier élément
    end; // while
    FirstB.Free; // appelle le destructeur de TB pour détruire le premier élément
  end; // if
end;

Lors de l'exécution, le code n' exécute pas le corps de la première boucle while, juste après la ligne
CurrentB := FirstB;

Voila, je vous ai tout dit. Alors si vous avez des indices pour trouver mon erreur...

Merci.

13 réponses

cs_rt15 Messages postés 3874 Date d'inscription mardi 8 mars 2005 Statut Modérateur Dernière intervention 7 novembre 2014 13
8 janv. 2008 à 12:58
Salut,


Ce serait pas plus simple si chaque parente supprime sa fille, qui supprime sa fille, qui supprime sa fille...


Free vérifie que le pointeur sur l'objet n'est pas à nil avant d'appeler le vrai destructeur.

destructor TA.Destroy();

begin

    FirstB.Free;
end;

destructor TB.Destroy();

begin

    NextB.Free;
end;
3
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
8 janv. 2008 à 13:40
Super bonne idée ! Pourquoi n'y avais-je pas pensé plus tôt.
Comme ça, pas besoin de boucler avec while.
Merci beaucoup pour l'idée.

Par contre, concernant la méthode "Free", elle n'appelait pas le destructeur de ma classe TB, alors je l'ai ré-écrite comme suit:

procedure TB.Free();
begin
  if self<>nil then
    self.Destroy();
end;

Je l'ai également ré-écrite pour ma classe TA. Comme ça, avec ta solution d'appel récursif au destructeur de TB, ça devrait bien tourner.

Merci rt15
0
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
8 janv. 2008 à 14:59
Ta méthode est efficace. J'ai pu ainsi trouver une erreur lors de la création de ma liste chaînée. C'est sans doute pour cela que ma méthode de destruction initiale ne fonctionnait pas. En effet, j'avais CurrentB.NextB=nil

Merci encore
0
Guillemouze Messages postés 991 Date d'inscription samedi 25 octobre 2003 Statut Membre Dernière intervention 29 août 2013 6
8 janv. 2008 à 19:14
ce n'est pas normal de reimplementer la méthode Free
as tu vérifier ta declaration du destructeur (dans l'interface) et de ses parents si c'est aussi des classes persos?
destructor Destroy;override;
en tout cas, reimplementer le destructeur ne fera que masquer un problème qui resurgira un jour ou l'autre.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
9 janv. 2008 à 17:16
Je ne vois pas en quoi c'est dangeureux de ré-écrire la méthode Free pour une classe, même si elle est générique, on peut la surcharger, non ?

Enfin je te fais confiance, j'ai effacé mes procédures Free. J'ai ajouté "override" à mes destructeurs de classe. Maintenant, l'appel à Free exécute bien le destructeur que j'ai écrit.

Une dernière question, est-ce bien "override", et non "overload" ? Si je comprend bien la nuance entre ces deux commandes, la première remplace tandis que la deuxième permet de compléter une méthode générique ?

Cordialement,
0
Guillemouze Messages postés 991 Date d'inscription samedi 25 octobre 2003 Statut Membre Dernière intervention 29 août 2013 6
9 janv. 2008 à 22:45
override permet de surcharger des methodes virtual (ou dynamic).
overload permet d'avoir 2 noms de fonctions identiques, aves des parametres differents.

si tu as une methode statique (non virtual), le pointeur sur cette methode est defini a la compilation.
si tu as une methode virtaul, le pointeur est defini a l'execution

imagine 2 classe :

TPere = class
  procedure Test; virtual ;
end;

TFils = class(Tpere)
  procedure Test; override;
end;
x :TPere.create; //> @(x.Test) = $1x :TFils.Create; // @(x.Test) $2

si tu ne met pas le virtual et le override, l'adresse sera du style
AdresseDeBaseDeLaClasse + AdresseDeLaMethodeDansLaClasse
a ce moment la, c'est la ligne de code qui te permet d'acceder a la methode qui definira laquelle utiliser (en gros, le type de ta variable):

var
    p: TPere;
    f: TFils;
begin
    f : = TFils.Create;
    p := f; // meme objet (de classe TFils)
    p.Test; //appel de la methode de la classe TPere
    f.Test; //appel de la methode de la classe TFils
alors que les 2 variables pointent vers le meme objet !

Pour le overload, tu peux avoir 2 methodes :
procedure Test(x: integer); overload;
procedure Test(x: string); overload;

ici, selon le type du parametre que tu passe a la fonction, l'implementation correspondante sera appelée.
0
Guillemouze Messages postés 991 Date d'inscription samedi 25 octobre 2003 Statut Membre Dernière intervention 29 août 2013 6
9 janv. 2008 à 22:54
Pour en revenir a ton destructeur, voila une petite explication:

Soit  A: TA;

A.Free provoque les appels suivants
TObject.Free
qui est implementé comme ca :
procedure TObject.Free;
begin
  Self.Destroy;
end;

vu que ta methode Destroy n'est pas override, l'adresse de la fonction Destroy trouvée depend du type de la variable self, a savoir TObject.
Donc la methode appelée est TObject.Destroy.

en revanche, si tu met override a ton TA.Destroy, l'adresse trouvée de la methode Destroy dans TObejct.Free est celle de TA, puisqu'elle a ete liée au Create de ton TA.

j'espere avoir ete clair
0
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
10 janv. 2008 à 09:45
Oula, le premier message était compliqué. Mais j'ai quand même saisi la différence entre overload et override. J'ai mieux compris ce qui se passait dans ma situation avec ton deuxième message. La destruction avec Free passe à présent toujours par le destructeur de ma classe, et non plus par celui de TObject.

Encore merci pour ton aide.
0
Guillemouze Messages postés 991 Date d'inscription samedi 25 octobre 2003 Statut Membre Dernière intervention 29 août 2013 6
10 janv. 2008 à 09:48
"... toujours par le destructeur de ma classe, et non plus par celui de TObject."

il passera par celui de TObject si tu met inherited dans le destructeur de ta classe
0
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
10 janv. 2008 à 10:35
Tu me conseillerais de ne pas mettre inherited si je gère moi même la libération de la mémoire allouée pour les objets dynamiques dans ma classe ?

Qu'en est-il pour le constructeur, inherited n'est peut-être pas non plus nécessaire ?
0
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
10 janv. 2008 à 10:35
Tu me conseillerais de ne pas mettre inherited si je gère moi même la libération de la mémoire allouée pour les objets dynamiques dans ma classe ?

Qu'en est-il pour le constructeur, inherited n'est peut-être pas non plus nécessaire ?
0
Guillemouze Messages postés 991 Date d'inscription samedi 25 octobre 2003 Statut Membre Dernière intervention 29 août 2013 6
10 janv. 2008 à 13:43
absolument pas ;)

a moins de le vouloir pour de bonnes raisons, il faut mettre le inherited dans toutes les fonctions override.

exemple :

destructor TA.Destroy; (override de TObject)
begin
  inherited;
  UnChampDeTA.Free;
end;

destructor TB.Destroy; (override de TA)

begin

  inherited;

  UnChampDeTB.Free;

end;

si tu ne met pas le inherited dans TB, l'objet UnChampdeTA ne sera pas detruit.
Le inherited dans le destroy de TA ne sert a rien, vu que TObject.Destroy ne contient aucun code. Cependant, si tu insere une classe intermediare entre TObject et TA ( TA class -> TA class(TC) ), le inherited servira a quelquechose. Donc inutile de le virer, meme si il ne sert a rien.
0
zwyx Messages postés 146 Date d'inscription jeudi 22 novembre 2007 Statut Membre Dernière intervention 21 mars 2016
10 janv. 2008 à 14:59
Bien compris. J'ai aussi rajouté override à mes construteurs de TA et TB, d'après ce que tu m'as expliqué.

Maintenant, tout est reglé concernant ce sujet.

Merci et Bonne journée
0
Rejoignez-nous