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

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;
Était-ce utile?

La solution

Je vois deux raisons pour lesquelles votre ensemble de déclarations d'origine ne doit pas compiler proprement:

  1. 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 utiliser reintroduce 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 suivante TCellPhone 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 utiliser virtual 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éthode TObject 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.

  2. Vous devriez obtenir une erreur dans TIPhone parce qu'il n'y a plus de constructeur un argument prioritaire. Vous l'avez caché dans TCellPhone. 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 sur toutes les déclarations originales - la première fois chaque signature distincte est introduit des 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

     

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'approvisionnement override

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.

constructor Create; Overload; <- utilisation surcharge ici

constructor Values; Overload; <- et ici

rappelez-vous de ne pas utiliser le même nom pour deux constructeurs différents

scroll top