delphi constructor delphi-5 constructor-chaining

Delphi: Comprender a los constructores



delphi-5 constructor-chaining (4)

Estoy buscando entender

  • virtual
  • anular
  • sobrecarga
  • reintroduce

cuando se aplica a constructores de objetos. Cada vez que agrego palabras clave al azar hasta que el compilador se apaga, y (después de 12 años de desarrollar con Delphi) prefiero saber lo que estoy haciendo, en lugar de intentar cosas al azar.

Dado un conjunto hipotético de objetos:

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 forma en que quiero que se comporten es probablemente obvia en las declaraciones, pero:

  • TComputer tiene el constructor simple, y los descendientes pueden anularlo
  • TCellPhone tiene un constructor alternativo, y los descendientes pueden anularlo
  • TiPhone invalida ambos constructores, llamando a la versión heredada de cada uno

Ahora ese código no compila. Quiero entender por qué no funciona. también quiero entender la forma correcta de anular constructores. ¿O quizás nunca podrías anular constructores? ¿O tal vez es perfectamente aceptable anular constructores? Tal vez nunca deberías tener múltiples constructores, quizás sea perfectamente aceptable tener múltiples constructores.

quiero entender el porqué Arreglarlo sería obvio.

Ver también

Editar: también estoy buscando un razonamiento virtual , override , overload , reintroduce . Porque al probar todas las combinaciones de palabras clave, el número de combinaciones explota:

  • virtual; sobrecarga;
  • virtual; anular;
  • anular; sobrecarga;
  • anular; virtual;
  • virtual; anular; sobrecarga;
  • virtual; sobrecarga; anular;
  • sobrecarga; virtual; anular;
  • anular; virtual; sobrecarga;
  • anular; sobrecarga; virtual;
  • sobrecarga; anular; virtual;
  • etc

Edición 2: supongo que deberíamos comenzar con "¿ es posible incluso la jerarquía de objetos? " De no ser así, ¿por qué no? Por ejemplo, ¿es fundamentalmente incorrecto tener un constructor de un antepasado?

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

esperaría que TCellPhone ahora tenga dos constructores. Pero no puedo encontrar la combinación de palabras clave en Delphi para que piense que es algo válido. ¿Estoy fundamentalmente equivocado al pensar que puedo tener dos constructores aquí en TCellPhone ?

Nota: Todo lo que se encuentra debajo de esta línea no es estrictamente necesario para responder la pregunta, pero ayuda a explicar mi pensamiento. Tal vez pueda ver, en base a mis procesos de pensamiento, qué pieza fundamental me falta que deja todo en claro.

Ahora estas declaraciones no se compilan:

//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;

Así que primero intentaré arreglar TCellPhone . Comenzaré agregando aleatoriamente la palabra clave de overload (sé que no quiero reintroduce porque eso ocultaría el otro constructor, que no quiero):

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

Pero eso falla: Field definition not allowed after methods or properties .

Sé por experiencia que, aunque no tengo un campo después de un método o propiedad, si invierto el orden de las palabras clave virtual y de overload : Delphi se cerrará:

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

Pero sigo recibiendo el error:

El método ''Crear'' oculta el método virtual de tipo base ''TComputer''

Así que intento eliminar ambas palabras clave:

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

Pero sigo recibiendo el error:

El método ''Crear'' oculta el método virtual de tipo base ''TComputer''

Entonces me resigno a intentar reintroduce :

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

Y ahora TCellPhone compila, pero ha empeorado las cosas para 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;

Ambos se están quejando de que no puedo anularlos, así que elimino la palabra clave de override :

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

Pero ahora la 2da creación dice que debe estar marcada con sobrecarga, lo cual hago (de hecho, marcaré ambos como sobrecarga, ya que sé lo que sucederá si no lo hago):

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

Todo es bueno en la sección de interface . Lamentablemente, mis implementaciones no funcionarán. Mi único constructor de parámetros de TiPhone no puede llamar al constructor heredado:

constructor TiPhone.Create(Cup: Integer); begin inherited Create(Cup); <---- Not enough actual parameters end;


Esta es una implementación operativa de las definiciones deseadas:

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.

con resultados:

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 primera parte está de acuerdo con this . La definición de los dos constructores TiPhone procede de la siguiente manera:

  • El primer constructor está sobrecargando uno de los dos constructores heredados y reemplazando a su hermano. Para lograr esto, use overload; override overload; override para sobrecargar el TCellPhone uno mientras TCellPhone el otro constructor.
  • Una vez hecho esto, el segundo constructor necesita una override simple para anular a su hermano.

Tenga en cuenta que no tengo Delphi 5, así que estoy basando mis respuestas en la versión más reciente, Delphi XE. No creo que eso realmente haga la diferencia aquí, pero si lo hace, se lo advirtió. :)

Esto se basa principalmente en http://docwiki.embarcadero.com/RADStudio/en/Methods , que es la documentación actual de cómo funcionan los métodos. Su archivo de ayuda de Delphi 5 probablemente tenga algo similar a esto también.

En primer lugar, un constructor virtual puede no tener mucho sentido aquí. Hay algunos casos en los que sí quieres esto, pero probablemente este no sea uno. Eche un vistazo a http://docwiki.embarcadero.com/RADStudio/en/Class_References para encontrar una situación en la que necesite un constructor virtual; sin embargo, si siempre conoce el tipo de objetos al codificar, no es así.

El problema que obtienes en tu constructor de 1 parámetro es que tu clase padre no tiene un constructor de 1 parámetro en sí mismo : los constructores heredados no están expuestos. No puede usar inherited para subir varios niveles en la jerarquía, solo puede llamar a su padre inmediato. Tendrá que llamar al constructor de 2 parámetros con algún valor predeterminado, o agregar un constructor de 1 parámetro a TCellPhone también.

En general, las cuatro palabras clave tienen los siguientes significados:

  • virtual : marque esto como una función en la que deseará el despacho en tiempo de ejecución (permite el comportamiento polimórfico). Esto es solo para la definición inicial, no cuando se anula en subclases.
  • override - Proporcionar una nueva implementación para un método virtual.
  • overload : marque una función con el mismo nombre que otra función, pero una lista de parámetros diferente.
  • reintroduce - Dile al compilador que realmente intentas ocultar un método virtual, en lugar de simplemente olvidarte de override .

El pedido requerido se detalla en la documentación:

Las declaraciones de métodos pueden incluir directivas especiales que no se usan con otras funciones o procedimientos. Las directivas solo deben aparecer en la declaración de clase, no en la declaración de definición, y siempre deben aparecer en el siguiente orden:

reintroducir; sobrecarga; Unión; convención de llamadas; abstracto; advertencia

donde el enlace es virtual, dinámico o sobrescrito; la convención de llamadas es register, pascal, cdecl, stdcall o safecall; y la advertencia es plataforma, obsoleta o biblioteca.


Veo dos razones por las cuales su conjunto original de declaraciones no debe compilarse limpiamente:

  1. Debería haber una advertencia en TCellPhone que su constructor oculta el método de la clase base. Esto se debe a que el método de la clase base es virtual , y al compilador le preocupa que esté introduciendo un nuevo método con el mismo nombre sin anular el método de la clase base. No importa que las firmas sean diferentes. Si su intención es de hecho ocultar el método de la clase base, entonces necesita usar la reintroduce en la declaración del descendiente, como lo mostró una de sus suposiciones ocultas. El único propósito de esa directiva es sofocar la advertencia; no tiene ningún efecto en el comportamiento en tiempo de ejecución.

    Ignorando lo que sucederá con TIPhone más adelante, la siguiente declaración de TCellPhone es lo que desea. Oculta el método ancestro, pero también quiere que sea virtual. No heredará la virtualidad del método ancestro porque son dos métodos completamente separados que simplemente tienen el mismo nombre. Por lo tanto, también debe usar virtual en la nueva declaración.

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

    El constructor de clase base, TComputer.Create , también está ocultando un método de su antecesor, TObject.Create , pero como el método en TObject no es virtual, el compilador no lo advierte. Ocultando métodos no virtuales ocurre todo el tiempo y generalmente no es notable.

  2. Debería obtener un error en TIPhone porque ya no hay ningún constructor de argumento único para anular. Lo escondiste en TCellPhone . Como desea tener dos constructores, reintroduce claramente no fue la elección correcta para usar antes. No quiere ocultar el constructor de la clase base; quieres aumentarlo con otro constructor.

    Como desea que ambos constructores tengan el mismo nombre, debe usar la directiva de overload . Esa directiva debe utilizarse en todas las declaraciones originales, la primera vez que cada firma distinta se introduce declaraciones posteriores en descendientes. Pensé que era obligatorio en todas las declaraciones (incluso en la clase base), y no hace daño hacer eso, pero supongo que no es obligatorio. Entonces, sus declaraciones deberían verse así:

    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;

La documentación moderna le dice a qué orden debe ir todo:

reintroducir ; sobrecarga ; vinculante ; convención de llamadas ; resumen advertencia

donde el enlace es virtual , dinámico o sobrescrito ; la convención de llamadas es register , pascal , cdecl , stdcall o safecall ; y la advertencia es plataforma , obsoleta o biblioteca .

Esas son seis categorías diferentes, pero en mi experiencia, es raro tener más de tres en cualquier declaración. (Por ejemplo, las funciones que necesitan convenciones de llamada especificadas probablemente no son métodos, por lo que no pueden ser virtuales.) Nunca recuerdo el orden; Nunca lo he visto documentado hasta hoy. En cambio, creo que es más útil recordar el propósito de cada directiva. Cuando recuerde qué directivas necesita para diferentes tareas, terminará con solo dos o tres, y luego es bastante sencillo experimentar para obtener un pedido válido. El compilador puede aceptar varias órdenes, pero no se preocupe: el orden no es importante para determinar el significado. Cualquier pedido que acepte el compilador tendrá el mismo significado que cualquier otro (a excepción de las convenciones de llamadas, si menciona más de uno, solo el último cuenta, así que no lo haga).

Entonces, solo debes recordar el propósito de cada directiva, y pensar cuáles no tienen sentido juntos. Por ejemplo, no puede usar reintroduce y override al mismo tiempo porque tienen significados opuestos. Y no puede usar virtual y override juntos porque uno implica al otro.

Si tiene muchas directivas acumulándose, siempre puede reducir la overload mientras elabora el resto de las directivas que necesita. Proporcione a sus métodos diferentes nombres, averigüe cuál de las otras directivas necesita por sí misma, y ​​luego agregue overload nuevo mientras les da todos los mismos nombres.


usar sobrecarga en ambos, es la forma en que lo hago, y funciona.

constructor Create; Overload constructor Create; Overload ; <- usa sobrecarga aquí

constructor Values; Overload; <- y aquí

recuerde no usar el mismo nombre para dos constructores diferentes