Delphi: comprendre les constructeurs

je cherche comprendre

"
  • virtuel
  • remplacer
  • surcharge
  • réintroduit

lorsqu'elle est appliquée aux constructeurs d'objets. Chaque fois que j'ajoute des mots-clés au hasard jusqu'à ce que le compilateur ferme - et (après 12 ans de développement avec Delphi) je préfère savoir ce que je fais, plutôt que d'essayer les choses au hasard.

donné un ensemble hypothétique d'objets:

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); virtual;
end;

TiPhone = class(TCellPhone)
public
    constructor Create(Cup: Integer); override;
    constructor Create(Cup: Integer; Teapot: string); override;
end;

la façon dont je veux qu'ils se comportent est probablement évidente d'après les déclarations, mais:

  • TComputer a le constructeur simple, et les descendants peuvent l'outrepasser
  • TCellPhone a un constructeur alternatif, et les descendants peuvent le remplacer
  • TiPhone l'emporte sur les deux constructeurs, appelant la version héritée de chaque

maintenant que le code ne se compile pas. je veux comprendre pourquoi ça ne fonctionne pas. je veux aussi comprendre la bonne façon d'outrepasser les constructeurs. Ou peut-être que vous ne pourriez jamais outrepasser les constructeurs? Ou peut-être est-il parfaitement acceptable d'outrepasser les constructeurs? Peut-être ne devriez-vous jamais avoir plusieurs constructeurs, peut-être est-il parfaitement acceptable d'avoir plusieurs constructeurs.

je veux comprendre le pourquoi . Le réparer serait alors évident.

voir aussi

  • Delphi: Comment masquer ancêtre des constructeurs?
  • rétablissement des fonctions dans le Delphi
  • Delphi: comment ajouter un constructeur différent à un descendant?

Edit: je suis également à la recherche pour obtenir un certain raisonnement sur l'ordre de virtual , override , overload , reintroduce . Car en essayant toutes les combinaisons de mots-clés, le nombre de combinaisons explose:

  • virtuel; surcharge;
  • virtuel; override;
  • override; surcharge;
  • override; virtual;
  • virtuel; outrepasser; surcharge;
  • virtuel; surcharge; override;
  • surcharge; virtual; override;
  • override; virtual; surcharge;
  • override; surcharge; virtual;
  • surcharge; override; virtual;
  • etc

Edit 2: je suppose que nous devrions commencer par " est la hiérarchie de l'objet même possible? " Si non, pourquoi pas? Par exemple, est-il fondamentalement incorrect d'avoir un constructeur à partir d'un ancêtre?

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); virtual;
end;

je pense que TCellPhone a maintenant deux constructeurs. Mais je ne trouve pas la combinaison de mots-clés dans Delphi pour lui faire penser que c'est une chose valide à faire. Je suis fondamentalement tort de penser que je peux avoir deux constructeurs, ici dans TCellPhone ?


Note: Tout ce qui est en dessous de cette ligne n'est pas strictement nécessaire pour répondre à la question-mais cela aide à expliquer ma pensée. Peut-être que vous pouvez voir, basé sur mes processus de pensée, ce que une pièce fondamentale qui me manque. tout devient clair.

Or ces déclarations ne compilent pas:

//Method Create hides virtual method of base type TComputer:
TCellPhone = class(TComputer)
   constructor Create(Cup: Integer; Teapot: string);  virtual;

//Method Create hides virtual method of base type TCellPhone:
TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); override;
   constructor Create(Cup: Integer; Teapot: string); overload;  <--------
end;

donc d'abord je vais essayer de réparer TCellPhone . je vais commencer par hasard ajouter le mot-clé overload (je sais que je ne veux pas reintroduce parce que cela cacherait l'autre constructeur, que je ne veux pas):

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;

mais qui échoue: Field definition not allowed after methods or properties .

je sais par expérience que, même si je n'ai pas de champ après une méthode ou une propriété, si j'inverse l'ordre du virtual et overload mots clés: Delphi se fermera:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); overload; virtual; 
end;

mais j'obtiens toujours l'erreur:

Méthode "Créer" des peaux de méthode virtuelle de la base type 'TComputer'

donc j'essaie de supprimer les deux mots clés:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string);
end;

mais j'obtiens toujours l'erreur:

Méthode "Créer" des peaux de méthode virtuelle de la base type 'TComputer'

alors je me résigne à essayer MAINTENANT reintroduce :

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;

et maintenant TCellPhone compile, mais il a fait les choses bien pire pour TiPhone:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); override; <-----cannot override a static method
   constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method
end;

les deux se plaignent que je ne peux pas les annuler, donc je supprime le override mot clé:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer);
   constructor Create(Cup: Integer; Teapot: string);
end;

mais maintenant le 2nd create dit qu'il doit être marqué avec surcharge, ce que je fais (en fait je marquerai les deux comme surcharge, puisque je sais ce qui se passera si Je ne le fais pas):

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); overload;
   constructor Create(Cup: Integer; Teapot: string); overload;
end;

tout est bon dans le interface section. Malheureusement, mes implémentations ne fonctionneront pas. Mon seul constructeur de paramètre de TiPhone ne peut pas appeler le constructeur hérité:

constructor TiPhone.Create(Cup: Integer);
begin
    inherited Create(Cup); <---- Not enough actual parameters
end;
31
demandé sur Community 2010-10-06 23:08:18

4 réponses

je vois deux raisons pour lesquelles votre ensemble original de déclarations ne devrait pas être compilé proprement:

  1. Il devrait y avoir un avertissement dans TCellPhone que son constructeur cache la méthode de la classe de base. C'est parce que la méthode de classe de base est virtuelle , et le compilateur s'inquiète que vous introduisez une nouvelle méthode avec le même nom sans passer par dessus la méthode de la classe de base. Peu importe que les signatures soient différentes. Si votre intention est en effet de cacher la méthode de la classe de base, alors vous devez utiliser reintroduce sur la déclaration de descendant, comme l'a montré une de vos suppositions aveugles. Le seul but de cette directive est d'étouffer l'avertissement; elle n'a aucun effet sur le comportement de l'exécution.

    ignorant ce qui va se passer avec TIPhone plus tard, la déclaration suivante TCellPhone est ce que vous voudriez. Il cache l'ancêtre de la méthode, mais vous voulez qu'il soit virtuel. Il n'héritera pas de la virtualité de la méthode de l'ancêtre parce qu'il s'agit de deux méthodes complètement distinctes qui ont le même nom. Par conséquent, vous devez utiliser virtual sur la nouvelle déclaration.

    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
    end;
    

    le constructeur de la classe de base, TComputer.Create , cache également une méthode de son ancêtre, TObject.Create , mais depuis la méthode en TObject n'est pas virtuel, le compilateur ne le prévient pas. Cacher des méthodes non-virtuelles se produit tout le temps et est généralement peu remarquable.

  2. vous devriez obtenir une erreur dans TIPhone parce qu'il n'y a plus de constructeur à un seul argument à remplacer. Tu l'as caché dans TCellPhone . Puisque vous voulez avoir deux constructeurs, reintroduce clairement n'était pas le bon choix à utiliser plus tôt. Vous ne voulez pas cacher le constructeur de la classe de base; vous voulez l'augmenter avec un autre constructeur.

    puisque vous voulez que les deux constructeurs aient le même nom, vous devez utiliser la directive overload . Cette directive doit être utilisée sur toutes les déclarations originales - la première fois que chaque signature distincte est introduite déclarations ultérieures dans les descendants. J'ai pensé qu'il était nécessaire sur toutes déclarations (même la classe de base), et ça ne fait pas de mal de le faire, mais je suppose que ce n'est pas nécessaire. Ainsi, vos déclarations devraient ressembler à ceci:

    TComputer = class(TObject)
    public
      constructor Create(Cup: Integer);
        overload; // Allow descendants to add more constructors named Create.
        virtual;  // Allow descendants to re-implement this constructor.
    end;
    
    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string);
        overload; // Add another method named Create.
        virtual;  // Allow descendants to re-implement this constructor.
    end;
    
    TiPhone = class(TCellPhone)
    public
      constructor Create(Cup: Integer);
        override; // Re-implement the ancestor's Create(Integer).
      constructor Create(Cup: Integer; Teapot: string);
        override; // Re-implement the ancestor's Create(Integer, string).
    end;
    

Moderne de la documentation raconte quel ordre, tout devrait aller dans:

réintroduire ; surcharge ; liaison ; convention d'appel ; abstrait ; avertissement

liaison est virtuel , dynamique , ou remplacer ; convention d'appel est s'inscrire , pascal , cdecl , stdcall , ou safecall ; et avertissement est plate-forme , obsolète , ou bibliothèque .

ce sont six catégories différentes, mais d'après mon expérience, il est rare d'en avoir plus de trois sur une déclaration. (Par exemple, les fonctions qui nécessitent des conventions d'appel spécifiées ne sont probablement pas des méthodes, donc elles ne peuvent pas être virtuelles.) Je ne me souviens jamais de l'ordre; je ne l'ai jamais vu documenté jusqu'à aujourd'hui. Au lieu de cela, je pense qu'il est plus utile de se souvenir de chaque directive objet . Quand vous vous rappelez quelles directives vous avez besoin pour différentes tâches, vous finirez avec seulement deux ou trois, et puis il est assez simple d'expérimenter pour obtenir un ordre valide. Le compilateur peut accepter plusieurs commandes, mais ne vous inquiétez pas - l'ordre n'est pas important pour déterminer le sens. Toute commande acceptée par le compilateur aura la même signification que n'importe quelle autre (sauf pour appeler conventions; si vous mentionnez plus d'une de ces conventions, seule la dernière compte, alors ne faites pas cela).

alors, il suffit de se rappeler le but de chaque directive, et de réfléchir à celles qui n'ont pas de sens ensemble. Par exemple, vous ne pouvez pas utiliser reintroduce et override en même temps parce qu'ils ont des significations opposées. Et vous ne pouvez pas utiliser virtual et override ensemble, parce que l'un implique l'autre.

si vous avez beaucoup de directives accumulent, vous pouvez toujours couper overload hors de l'image pendant que vous travaillez le reste des directives dont vous avez besoin. Donnez à vos méthodes des noms différents, déterminez lequel des autres directives ils ont besoin par eux-mêmes, puis Ajouter overload arrière pendant que vous leur donnez tous les mêmes noms à nouveau.

15
répondu Rob Kennedy 2010-10-06 21:49:17

notez que je n'ai pas Delphi 5, donc je base mes réponses sur la dernière version, Delphi XE. Je ne pense pas que cela fasse vraiment une différence ici, mais si c'est le cas, vous avez été prévenu. :)

cela est principalement basé sur http://docwiki.embarcadero.com/RADStudio/en/Methods , qui est la documentation actuelle sur le fonctionnement des méthodes. Votre fichier D'aide Delphi 5 a probablement quelque chose de similaire.

First hors, un constructeur virtuel ne peut pas faire beaucoup de sens ici. Il y a quelques cas où vous voulez cela, mais ce n'en est probablement pas un. Jetez un oeil à http://docwiki.embarcadero.com/RADStudio/en/Class_References pour une situation où vous avez besoin d'un constructeur virtuel - si vous connaissez toujours le type de vos objets lors du codage, vous ne le savez pas.

le problème que vous obtenez alors dans votre constructeur à 1 paramètre est que votre classe de parent ne possède pas de constructeur à 1 paramètre lui - même - les constructeurs hérités ne sont pas exposés. Vous ne pouvez pas utiliser inherited pour monter plusieurs niveaux dans la hiérarchie, vous pouvez seulement appeler votre parent immédiat. Vous devrez appeler le constructeur à 2 paramètres avec une valeur par défaut, ou ajouter un constructeur à 1 paramètre à TCellPhone.

en général, les quatre mots clés ont le sens suivant:

  • virtual - Marque il s'agit d'une fonction où vous voulez envoyer le temps d'exécution (permet le comportement polymorphe). Ce n'est que pour la définition initiale, et non lorsqu'il y a dépassement dans les sous-classes.
  • override - fournir une nouvelle implémentation pour une méthode virtuelle.
  • overload - marquer une fonction avec le même nom qu'une autre fonction, mais une liste de paramètres différente.
  • reintroduce - dites au compilateur que vous avez réellement prévu pour cacher une méthode virtuelle, au lieu de simplement oublier de fournir override .

l'ordre requis est détaillé dans la documentation:

Les déclarations de méthode

peuvent inclure: directives spéciales non utilisées avec d'autres fonctions ou procédures. Les Directives doivent apparaître dans la classe déclaration, pas dans la définition de déclaration, et devrait toujours être énumérés dans l'ordre suivant:

réintroduire; surcharge; liaison; convention d'appel; abstrait; avertissement

lorsque la liaison est virtuelle, dynamique ou outrepasser; la convention d'appel est registre, pascal, cdecl, stdcall, ou safecall; et l'alerte est la plate-forme, dépréciée, ou bibliothèque.

5
répondu Michael Madsen 2010-10-06 19:52:38

il s'agit d'une mise en œuvre pratique des définitions recherchées:

program OnConstructors;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;

TiPhone = class(TCellPhone)
public
    constructor Create(Cup: Integer); overload; override;
    constructor Create(Cup: Integer; Teapot: string); override;
end;

{ TComputer }

constructor TComputer.Create(Cup: Integer);
begin
  Writeln('Computer: cup = ', Cup);
end;

{ TCellPhone }

constructor TCellPhone.Create(Cup: Integer; Teapot: string);
begin
  inherited Create(Cup);
  Writeln('Cellphone: teapot = ', Teapot);
end;

{ TiPhone }

constructor TiPhone.Create(Cup: Integer);
begin
  inherited Create(Cup);
  Writeln('iPhone: cup = ', Cup);
end;

constructor TiPhone.Create(Cup: Integer; Teapot: string);
begin
  inherited;
  Writeln('iPhone: teapot = ', Teapot);
end;

var
  C: TComputer;

begin

  C := TComputer.Create(1);
  Writeln; FreeAndNil(C);

  C := TCellPhone.Create(2);
  Writeln; FreeAndNil(C);
  C := TCellPhone.Create(3, 'kettle');
  Writeln; FreeAndNil(C);

  C := TiPhone.Create(4);
  Writeln; FreeAndNil(C);
  C := TiPhone.Create(5, 'iPot');

  Readln; FreeAndNil(C);

  end.

avec les résultats:

Computer: cup = 1

Computer: cup = 2

Computer: cup = 3
Cellphone: teapot = kettle

Computer: cup = 4
iPhone: cup = 4

Computer: cup = 5
Cellphone: teapot = iPot
iPhone: teapot = iPot

la première partie est conforme à ce . La définition des TiPhone deux constructeurs procède alors comme suit:

  • le premier constructeur surcharge un des deux constructeurs hérite et supplante son frère. De pour ce faire, utilisez overload; override pour surcharger le TCellPhone tout en dépassant l'autre constructeur.
  • Cela fait, le second constructeur a besoin d'un simple override pour passer outre son frère.
2
répondu Muhammad Alkarouri 2017-05-23 11:44:25

utilisez surcharge sur les deux, c'est comme ça que je le fais, et ça marche.

constructor Create; Overload ; <-- utiliser la surcharge ici

constructor Values; Overload; < --

n'oubliez pas de ne pas utiliser le même nom pour deux constructeurs différents

0
répondu Damon 2016-08-19 20:48:42