web-dev-qa-db-fra.com

Delphi: comprendre les constructeurs

je cherche à comprendre

  • virtuel
  • passer outre
  • surcharge
  • réintroduire

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

É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 veux qu'ils se comportent est probablement évidente dans les déclarations, mais:

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

Maintenant, ce code ne compile pas. je veux comprendre pourquoi ça ne marche pas. Je veux aussi comprendre la bonne façon de remplacer les constructeurs. Ou peut-être ne pourriez-vous jamais remplacer les constructeurs? Ou peut-être est-il parfaitement acceptable de remplacer les constructeurs? Peut-être que vous ne devriez jamais avoir plusieurs constructeurs, peut-être est-il parfaitement acceptable d'avoir plusieurs constructeurs.

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

Voir également

Edit: Je cherche également à obtenir un raisonnement de l’ordre suivant: virtual, override, overload, reintroduce. Parce qu'en essayant toutes les combinaisons de mots-clés, le nombre de combinaisons explose:

  • virtuel; surcharge;
  • virtuel; passer outre;
  • passer outre; surcharge;
  • passer outre; virtuel;
  • virtuel; passer outre; surcharge;
  • virtuel; surcharge; passer outre;
  • surcharge; virtuel; passer outre;
  • passer outre; virtuel; surcharge;
  • passer outre; surcharge; virtuel;
  • surcharge; passer outre; virtuel;
  • etc

Edit 2: Je suppose que nous devrions commencer par "la hiérarchie des objets est-elle donnée, est-elle possible??}" Si non, pourquoi? 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;

je pense que TCellPhone a maintenant deux constructeurs. Mais je ne parviens pas à trouver la combinaison de mots-clés dans Delphi pour faire croire que c'est une chose valable à faire. Suis-je fondamentalement dans l'erreur en pensant que je peux avoir deux constructeurs ici dans TCellPhone?


Remarque: Tout ce qui se trouve sous cette ligne n'est pas strictement nécessaire pour répondre à la question - mais cela aide à expliquer ma pensée. Vous pouvez peut-être voir, sur la base de mes processus de pensée, quel élément fondamental me manque et qui clarifie tout.

Maintenant, 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 ajouter au hasard le mot clé overload (je sais que je ne veux pas reintroduce car cela masquerait l'autre constructeur, ce que je ne veux pas):

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

Mais cela é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 des mots clés virtual et overload: Delphi va se taire:

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

Mais j'obtiens toujours l'erreur:

La méthode 'Create' masque la méthode virtuelle du type de base 'TComputer'

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

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

Mais j'obtiens toujours l'erreur:

La méthode 'Create' masque la méthode virtuelle du type de base 'TComputer'

Donc, je me résigne à essayer maintenant reintroduce:

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

Et maintenant, TCellPhone est compilé, mais cela a encore aggravé la situation de 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, alors je supprime le mot clé override:

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

Mais maintenant, la deuxième création dit qu'il doit être marqué par une surcharge, ce que je fais (en fait, je vais marquer les deux comme une 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 la section interface. Malheureusement, mes implémentations ne fonctionnent pas. Mon constructeur à un seul 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;
32
Ian Boyd

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

  1. Il devrait y avoir un warning dans TCellPhone que son constructeur cache la méthode de la classe de base. En effet, la méthode de la classe de base est virtuelle et le compilateur craint que vous introduisiez une méthode nouvelle avec le même nom sans remplacer la méthode de la classe de base. Peu importe que les signatures diffèrent. Si votre intention est effectivement de masquer la méthode de la classe de base, vous devez utiliser reintroduce dans la déclaration du descendant, comme l’a montré l’un de vos suppositions aveugles. Le seul but de cette directive est de réprimer l'avertissement; cela n'a aucun effet sur le comportement au moment de l'exécution.

    Ignorant ce qui va se passer avec TIPhone plus tard, la déclaration TCellPhone suivante est ce que vous voudriez. Il cache la méthode ancêtre, mais vous voulez aussi qu'elle soit virtuelle. Elle n'héritera pas de la virtualité de la méthode ancêtre car ce sont deux méthodes complètement séparées qui portent le même nom. Par conséquent, vous devez également utiliser virtual sur la nouvelle déclaration.

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

    Le constructeur de classe de base, TComputer.Create, masque également une méthode de son ancêtre, TObject.Create, mais comme la méthode dans TObject n'est pas virtuelle, le compilateur n'en avertit pas. Masquer les méthodes non virtuelles se produit tout le temps et est généralement sans particularité.

  2. Vous devriez obtenir un erreur dans TIPhone car il n'y a plus de constructeur à un argument à remplacer. Vous l'avez 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. Je pensais que c'était obligatoire sur les toutes déclarations (même la classe de base), et cela ne fait pas de mal de le faire, mais je suppose que ce n'est pas obligatoire. 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 l'ordre dans lequel tout doit être placé:

réintroduit ; surcharge ; contraignant; convention d'appel; abstract ; Attention

liaison est virtuel , dynamique ou override ; convention d'appel est registre , Pascal , cdecl , stdcall ou safecall ; et warning est platform , 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 dans une déclaration. (Par exemple, les fonctions nécessitant des conventions d'appel spécifiées ne sont probablement pas des méthodes, elles ne peuvent donc 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 rappeler le objet de chaque directive. Lorsque vous vous souvenez des directives dont vous avez besoin pour différentes tâches, vous n’en obtenez que deux ou trois, 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'a pas d'importance dans la détermination du sens. Toute commande acceptée par le compilateur aura la même signification que toute autre (à l'exception des conventions d'appel; si vous en indiquez plusieurs, seul le dernier compte, alors ne le faites pas).

Dans ce cas, vous devez simplement vous rappeler le but de chaque directive et déterminer celles qui n’ont aucun sens ensemble. Par exemple, vous ne pouvez pas utiliser reintroduce et override en même temps car ils ont des significations opposées. Et vous ne pouvez pas utiliser virtual et override ensemble parce que l'un implique l'autre.

Si de nombreuses directives s'empilent, vous pouvez toujours supprimer overload pendant que vous élaborez le reste des directives dont vous avez besoin. Donnez à vos méthodes des noms différents, déterminez laquelle des autres directives dont elles ont besoin par elles-mêmes, puis ajoutez overload en arrière en leur redonnant tous les mêmes noms.

15
Rob Kennedy

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

Ceci 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.

Tout d'abord, un constructeur virtuel peut ne pas avoir beaucoup de sens ici. Vous voulez parfois cela, mais ce n’est probablement pas le cas. Jetez un coup d’oeil à http://docwiki.embarcadero.com/RADStudio/fr/Class_References pour une situtation où vous avez besoin d’un constructeur virtuel - si vous connaissez toujours le type de vos objets lors de la 't.

Le problème que vous obtenez alors dans votre constructeur à 1 paramètre est que votre classe parent n'a 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 uniquement appeler votre parent immédiat. Vous devrez appeler le constructeur à 2 paramètres avec une valeur par défaut ou ajouter également un constructeur à 1 paramètre à TCellPhone.

En général, les quatre mots clés ont les significations suivantes:

  • virtual - Marquez cette fonction comme une fonction pour laquelle vous souhaitez une répartition au moment de l'exécution (permet le comportement polymorphe). Ceci ne concerne que la définition initiale, pas lors de la substitution dans des sous-classes.
  • override - Fournit une nouvelle implémentation pour une méthode virtuelle.
  • overload - Marque une fonction avec le même nom comme une autre fonction, mais une liste de paramètres différente.
  • reintroduce - Indiquez au compilateur que vous destiné cache une méthode virtuelle au lieu de simplement oublier de fournir override.

La commande requise est détaillée dans la documentation:

Les déclarations de méthode peuvent inclure des directives spéciales qui ne sont pas utilisées avec d'autres fonctions ou procédures. Les directives ne doivent apparaître que dans la déclaration de classe, pas dans la déclaration de définition, et doivent toujours être répertoriées dans l'ordre suivant:

réintroduire; surcharge; contraignant; convention d'appel; abstrait; Attention

où la liaison est virtuelle, dynamique ou forcée; convention d'appel est register, Pascal, cdecl, stdcall ou safecall; et warning est la plate-forme, obsolète ou la bibliothèque.

5
Michael Madsen

Ceci est une implémentation fonctionnelle des définitions souhaité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 conforme à this . La définition de TiPhone deux constructeurs se déroule ensuite comme suit:

  • Le premier constructeur surcharge one des deux constructeurs hérités et substitue son frère. Pour ce faire, utilisez overload; override pour surcharger la TCellPhone en substituant l'autre constructeur.
  • Cela étant fait, le second constructeur a besoin d'une simple override pour remplacer son frère.
2

utiliser la surcharge sur les deux, c'est la façon dont je le fais, et ça marche.

constructor Create; Overload; <- utilisez la surcharge ici

constructor Values; Overload; <- et ici

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

0
Damon