Delphi: constructeurs Comprendre
-
28-09-2019 - |
Question
Je cherche à comprendre
- virtuel
- override
- surcharge
- reintroduce
lorsqu'il est appliqué à des constructeurs d'objets. Chaque fois que j'ajouter des mots-clés au hasard jusqu'à ce que le compilateur se ferme vers le haut -. Je préfère savoir ce que je fais, plutôt que d'essayer au hasard des choses et (après 12 ans de développement avec Delphi)
Étant 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 les veux est probablement évidente se comporter des déclarations, mais:
-
TComputer
a le constructeur simple, et descendants peut la remplacer -
TCellPhone
a un constructeur de remplacement, et descendants peut la remplacer -
TiPhone
les deux constructeurs overrides, appelant la version héritée de chaque
Maintenant que le code ne compile pas. Je veux comprendre pourquoi il ne fonctionne pas. Je veux aussi comprendre la bonne façon de passer outre les constructeurs. Ou vous pourriez peut-être jamais remplacer les constructeurs? Ou peut-être il est tout à fait acceptable pour les constructeurs de remplacement? Peut-être que vous ne devriez jamais avoir plusieurs constructeurs, peut-être il est parfaitement acceptable d'avoir plusieurs constructeurs.
Je veux comprendre le pourquoi . Fixing il serait alors évident.
Voir aussi
- Delphi: Comment cacher ancêtre constructeurs
- Réintroduire fonctions Delphi
- Delphi: Comment ajouter un constructeur différent un descendant
Modifier Je cherche aussi à obtenir un raisonnement sur l'ordre de virtual
, override
, overload
, reintroduce
. Parce que lorsque vous essayez toutes les combinaisons de mots-clés, le nombre de combinaisons: explosion
- virtuel; surcharge;
- virtuel; remplacer;
- override; surcharge;
- override; virtuelle;
- virtuel; passer outre; surcharge;
- virtuel; surcharge; remplacer;
- surcharge; virtuel; remplacer;
- override; virtuel; surcharge;
- override; surcharge; virtuelle;
- surcharge; passer outre; virtuelle;
- etc
Edit 2: Je suppose que nous devrions commencer par " est la hiérarchie des objets donnée même possible ?" Sinon, pourquoi pas? Par exemple, est-il fondamentalement incorrect d'avoir un constructeur 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;
J'attendre à ce que TCellPhone
a maintenant deux constructeurs. Mais je ne peux pas trouver la combinaison de mots-clés dans Delphi pour le faire que ce soit une chose valable de le faire. Suis-je fondamentalement tort de penser que je peux avoir deux constructeurs ici TCellPhone
?
Remarque: Tout dessous de cette ligne n'est pas strictement nécessaire pour répondre à la question - mais il aide à expliquer ma pensée. Peut-être que vous pouvez voir, en fonction de mes processus de pensée, ce élément fondamental je suis absent que fait tout 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;
Alors d'abord je vais essayer de fixation TCellPhone
. Je vais commencer par ajouter au hasard le mot-clé overload
(je sais que je ne veux pas reintroduce
parce que cela cacher 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 un champ après une méthode ou une propriété, si j'inverser l'ordre des mots-clés virtual
et overload
: Delphi taire:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
Mais je reçois toujours l'erreur:
La méthode « créer » peaux méthode virtuelle de type de base « TCOMPuter '
Alors j'essayez de supprimer les deux mots-clés:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
end;
Mais je reçois toujours l'erreur:
La méthode 'créer' peaux méthode virtuelle de la base de type 'TComputer'
Alors, je me résigne à essayer maintenant reintroduce
:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;
Et maintenant compiles TCellPhone, mais il a fait des choses bien pires 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 remplacer, donc je supprimer le mot-clé override
:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
constructor Create(Cup: Integer; Teapot: string);
end;
Mais le 2 créer dit qu'il doit être marqué avec la surcharge, que je ne (en fait, je vais marquer à la fois comme la surcharge, car 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;
Toutes tout est bon dans la section interface
. 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;
La solution
Je vois deux raisons pour lesquelles votre ensemble de déclarations d'origine ne doit pas compiler proprement:
-
Il devrait y avoir un avertissement dans
TCellPhone
que son constructeur peaux la méthode de la classe de base. En effet, la méthode est la classe de base , et les soucis du compilateur que vous introduisez de nouvelle méthode avec le même nom sans redéfinissant la méthode classe de base. Peu importe que les signatures diffèrent. Si votre intention est en effet de cacher la méthode de la classe de base, alors vous devez utiliserreintroduce
sur la déclaration descendant, comme l'un de vos suppositions aveugles ont montré. Le seul but de cette directive est d'étouffer l'avertissement; elle n'a aucun effet sur le comportement d'exécution.Ignorant ce qui va se passer avec
TIPhone
plus tard, la déclaration suivanteTCellPhone
de est ce que vous voulez. Il cache la méthode ancêtre, mais vous voulez qu'il soit virtuel. Il ne sera pas hériter de la virtualité de la méthode ancêtre, car ils sont deux méthodes complètement distinctes qui arrivent juste d'avoir le même nom. , Vous devez donc utiliservirtual
sur la nouvelle déclaration ainsi.TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual; end;
Le constructeur de classe de base,
TComputer.Create
, se cache également une méthode de son ancêtre,TObject.Create
, mais étant donné que la méthodeTObject
n'est pas virtuel, le compilateur n'avertit pas. Hiding méthodes non virtuelles arrive tout le temps et est en général rien de remarquable. -
Vous devriez obtenir une erreur dans
TIPhone
parce qu'il n'y a plus de constructeur un argument prioritaire. Vous l'avez caché dansTCellPhone
. Puisque vous voulez avoir deux constructeurs,reintroduce
clairement n'a pas le bon choix d'utiliser plus tôt. Vous ne voulez pas cacher le constructeur de classe de base; vous voulez 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 besoins à utiliser surtoutes les déclarations originales - la première fois chaque signature distincte est introduitdes déclarations ultérieures en descendants. Je pensais que c'était nécessaire sur tous déclarations (même la classe de base), et il ne fait pas de mal à 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;
documentation moderne indique que tout devrait aller dans l'ordre:
reintroduce ; surcharge ; liaison ; convention d'appel ; résumé ; avertissement
où liaison est virtuel , dynamique ou override ; convention d'appel enregistrer , pascals , cdecl , stdcall ou safecall ; et avertissement est
plateforme , Obsolète , ou bibliothèque .
Ce sont six catégories différentes, mais dans mon expérience, il est rare d'avoir plus de trois sur toute déclaration. (Par exemple, les fonctions qui ont besoin d'appeler les conventions spécifiées ne sont probablement pas des méthodes, de sorte qu'ils ne peuvent pas être virtuel.) Je ne me souviens de l'ordre; Je ne l'ai jamais vu documenté jusqu'à aujourd'hui. Au lieu de cela, je pense que c'est plus utile de se rappeler de chaque directive fin . Lorsque vous vous souvenez que les directives dont vous avez besoin pour des tâches différentes, vous vous retrouverez avec seulement deux ou trois, et il est assez simple d'expérimenter pour obtenir une ordonnance valide. Le compilateur peut accepter plusieurs commandes, mais ne vous inquiétez pas - l'ordre n'a pas d'importance pour déterminer le sens. Toute commande le compilateur accepts le même sens que tout autre (sauf pour les conventions d'appel, si vous mentionnezplus d'un de ceux-ci, seul le dernier compte, il ne faut pas le faire).
Alors, vous venez de rappeler l'objet de chaque directive, et penser à ceux qui ne font pas de sens ensemble. Par exemple, vous ne pouvez pas utiliser reintroduce
et override
en même temps parce qu'ils ont des sens opposés. Et vous ne pouvez pas utiliser ensemble virtual
et override
parce que l'un implique l'autre.
Si vous avez beaucoup de directives empilant, vous pouvez toujours overload
découpe de l'image pendant que vous travaillez le reste des directives dont vous avez besoin. Donnez à vos méthodes des noms différents, figure sur laquelle des autres directives dont ils ont besoin eux-mêmes, puis rajouter de overload
pendant que vous leur donnez tous les mêmes noms à nouveau.
Autres conseils
Notez que je n'ai pas Delphi 5, donc je fonde mes réponses de la nouvelle version, Delphi XE. Je ne pense pas que va vraiment faire une différence, mais si elle le fait, vous avez été prévenu. :)
Ceci est principalement basée sur http://docwiki.embarcadero.com/RADStudio/en/Methods , qui est la documentation actuelle de la façon dont fonctionnent les méthodes. Votre fichier d'aide Delphi 5 a probablement quelque chose de semblable à cela aussi.
Tout d'abord, un constructeur virtuel ne peut pas faire beaucoup de sens ici. Il y a quelques cas où vous ne voulez, mais cela est probablement pas. Jetez un oeil à http://docwiki.embarcadero.com/RADStudio/en/Class_References pour une situtation où vous avez besoin d'un constructeur virtuel - si vous connaissez toujours le type de vos objets lors du codage, cependant, vous ne pas
. Le problème que vous obtenez alors dans votre constructeur 1 paramètre est que votre classe parente n'a pas de constructeur 1-paramètre lui-même - constructeurs hérités ne sont pas exposés. Vous ne pouvez pas utiliser inherited
pour remonter plusieurs niveaux dans la hiérarchie, vous ne pouvez appeler votre parent immédiat. Vous aurez besoin d'appeler le constructeur 2 paramètres avec une valeur par défaut, ou ajoutez un constructeur 1 paramètre à TCellPhone ainsi.
En général, les quatre mots-clés ont les significations suivantes:
-
virtual
- Marquer ce en fonction où vous voulez dispatching exécution (permet le comportement polymorphique). Ceci est seulement pour la définition initiale, et non pas lors de la substitution dans les sous-classes. -
override
-. Fournir une nouvelle implémentation d'une méthode virtuelle -
overload
-. Marquer une fonction avec le même nom qu'une autre fonction, mais une liste de paramètres différents -
reintroduce
-. Dites au compilateur que vous avez réellement destiné pour cacher une méthode virtuelle, au lieu de simplement oublier d'approvisionnementoverride
La commande requise est détaillée dans la documentation:
déclarations procédé peut comprendre directives spéciales qui ne sont pas utilisés avec d'autres fonctions ou procédures. Les directives doivent apparaître dans la classe déclaration, et non dans la définition déclaration, et devrait toujours être énumérés dans l'ordre suivant:
reintroduce; surcharge; contraignant; convention d'appel; abstrait; avertissement
où la liaison est virtuel, dynamique, ou passer outre; convention d'appel est registre, pascal, cdecl, stdcall, ou safecall; et l'avertissement est plate-forme, Obsolète, ou d'une bibliothèque.
Ceci est une implémentation fonctionnelle 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 des 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 en conformité avec
surcharge d'utilisation à la fois, c'est la façon dont je le fais, et cela fonctionne. rappelez-vous de ne pas utiliser le même nom pour deux constructeurs différents constructor Create; Overload
; <- utilisation surcharge ici constructor Values; Overload;
<- et ici