delphi inheritance class records

¿Por qué no pueden heredar los registros de Delphi?



inheritance class (8)

De acuerdo con esta pregunta, hay dos tipos de herencia: herencia de interfaz y herencia de implementación.

Herencia de interfaz generalmente implica polimorfismo. Esto significa que si B se deriva de A, entonces los valores del tipo B se pueden almacenar en ubicaciones del tipo A. Esto es problemático para los tipos de valor (registros similares) en lugar de los tipos de referencia, debido a la segmentación. Si B es más grande que A, entonces almacenarlo en una ubicación de tipo A truncará el valor, se perderán todos los campos que B haya agregado en su definición más allá de los de A.

La herencia de implementación es menos problemática desde esta perspectiva. Si Delphi tuviera herencia de registro pero solo de la implementación, y no de la interfaz, las cosas no estarían tan mal. El único problema es que simplemente haciendo un valor de tipo A un campo de tipo B hace la mayor parte de lo que querría de la herencia de implementación.

El otro tema son los métodos virtuales. El envío del método virtual requiere algún tipo de etiqueta por valor para indicar el tipo de tiempo de ejecución del valor, de modo que se pueda descubrir el método sobrescrito correcto. Pero los registros no tienen ningún lugar para almacenar este tipo: los campos del registro son todos los campos que tiene. Los objetos (el antiguo tipo Turbo Pascal) pueden tener métodos virtuales porque tienen un VMT: el primer objeto en la jerarquía para definir un método virtual agrega implícitamente un VMT al final de la definición del objeto, haciéndolo crecer. Pero los objetos de Turbo Pascal tienen el mismo problema de corte que se describe anteriormente, lo que los hace problemáticos. Los métodos virtuales en los tipos de valor requieren efectivamente la herencia de la interfaz, lo que implica el problema de corte.

Por lo tanto, para admitir correctamente la herencia de la interfaz de registro, necesitaríamos algún tipo de solución al problema de corte. El boxeo sería un tipo de solución, pero generalmente requiere que la recolección de basura sea utilizable, e introduciría ambigüedad en el lenguaje, donde puede que no esté claro si está trabajando con un valor o una referencia, un poco como Integer vs Int en Java con autoboxing. Al menos en Java hay nombres separados para los "tipos" de tipos de valor en caja o sin caja. Otra forma de hacer el boxeo es como Google Go con sus interfaces, que es un tipo de herencia de interfaz sin herencia de implementación, pero requiere que las interfaces se definan por separado y todas las ubicaciones de las interfaces son referencias. Los tipos de valor (por ejemplo, registros) están encuadrados cuando se hace referencia por una referencia de interfaz. Y, por supuesto, Go también tiene recolección de basura.

Algo que me he preguntado durante mucho tiempo: ¿por qué los registros de Delphi no pueden tener herencia (y, por lo tanto, todas las otras características importantes de la POO)?

Básicamente, esto haría que los registros de la versión de las clases asignadas a la pila, al igual que las clases de C ++, dejen obsoletos los "objetos" (nota: no instancias). No veo nada problemático con ello. Esta también sería una buena oportunidad para implementar declaraciones a plazo para los registros (lo cual todavía me desconcierta de por qué todavía falta).

¿Ves algún problema con esto?


El único problema que veo (y podría ser miope o incorrecto) es el propósito. Los registros son para almacenar datos, mientras que los objetos son para manipular y usar dichos datos. ¿Por qué un casillero de almacenamiento necesita rutinas de manipulación?


En tiempos pasados ​​he usado objetos (¡no clases!) Como registros con herencia.

A diferencia de lo que algunas personas aquí están diciendo, hay razones legítimas para esto. El caso en el que lo hice involucró dos estructuras de fuentes externas (API, no nada fuera del disco; necesitaba el registro con formato completo en la memoria), el segundo de los cuales simplemente extendió el primero.

Sin embargo, estos casos son muy raros.


Esto está relacionado con el tema de su pregunta y se relaciona con la extensión de la funcionalidad de los tipos de registro y clase a través de los asistentes de clase y registro. De acuerdo con la documentación de Embarcadero sobre esto, puede extender una clase o un registro (pero los ayudantes no admiten la sobrecarga del operador). Básicamente, puede ampliar la funcionalidad en términos de métodos de miembro pero no datos de miembros). Son compatibles con los campos de clase a los que se puede acceder a través de captadores y configuradores de la forma habitual, aunque no he probado esto. Si quisiera interconectar el acceso a los datos de la clase o el registro al que le estaba agregando el ayudante, probablemente podría lograrlo (es decir, activar un evento o señal cuando se modificaron los datos de los miembros de la clase o registro original). Sin embargo, no pudo implementar la ocultación de datos, pero sí le permite anular una función miembro existente de la clase original.

p.ej. Este ejemplo funciona en Delphi XE4. Cree una nueva aplicación de formularios VCL y reemplace el código de Unit1 con el siguiente código:

interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Types; type TMyArray2D = array [0..1] of single; TMyVector2D = record public function Len: single; case Integer of 0: (P: TMyArray2D); 1: (X: single; Y: single;); end; TMyHelper = record helper for TMyVector2D function Len: single; end; TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; implementation function TMyVector2D.Len: Single; begin Result := X + Y; end; function TMyHelper.Len: single; begin Result := Sqrt(Sqr(X) + Sqr(Y)); end; procedure TestHelper; var Vec: TMyVector2D; begin Vec.X := 5; Vec.Y := 6; ShowMessage(Format(''The Length of Vec is %2.4f'',[Vec.Len])); end; procedure TForm1.Form1Create(Sender: TObject); begin TestHelper; end;

Observe que el resultado es 7.8102 en lugar de 11. Esto muestra que puede ocultar los métodos de miembro de la clase o registro original con una clase o asistente de registro.

Entonces, de una manera, simplemente trataría el acceso a los miembros de datos originales de la misma manera que lo haría al cambiar los valores desde dentro de la unidad en la que se declara una clase al cambiar a través de las propiedades en lugar de los campos directamente, de modo que las acciones apropiadas sean tomadas por Los captadores y definidores de esos datos.

Gracias por hacer la pregunta. Ciertamente aprendí mucho al tratar de encontrar la respuesta y también me ayudó mucho.

Brian Joseph Johns


Podrías intentar usar la palabra clave del objeto Delphi para eso. Básicamente son hereditarios, pero se comportan mucho más como registros que a clases.

Vea este thread y esta description .


Porque los registros no tienen VMT (tabla de métodos virtuales).


Tienes razón, agregar herencia a los registros esencialmente los convertiría en clases de C ++. Y esa es tu respuesta allí mismo: no se hace porque eso sería algo horrible de hacer. Puede tener tipos de valores asignados a la pila, o puede tener clases y objetos, pero mezclar los dos es una muy mala idea. Una vez que lo haces, terminas con todo tipo de problemas de administración de por vida y tienes que construir hacks feos como el patrón RAII de C ++ en el lenguaje para lidiar con ellos.

Línea inferior: si desea un tipo de datos que puede ser heredado y extendido, use clases. Para eso están ellos.

EDITAR: En respuesta a la pregunta de Cloud, esto no es realmente algo que se pueda demostrar a través de un solo ejemplo simple. Todo el modelo de objetos de C ++ es un desastre. Puede que no se vea como uno de cerca; tienes que entender varios problemas interconectados para comprender realmente el panorama general. RAII es solo el desorden en la parte superior de la pirámide. Tal vez escriba una explicación más detallada en mi blog esta semana, si tengo tiempo.


Registros y Clases / Objetos son dos cosas muy diferentes en Delphi. Básicamente, un registro de Delphi es una estructura en C: Delphi incluso admite la sintaxis para hacer cosas como tener un registro al que se puede acceder como 4 enteros de 16 bits o como 2 enteros de 32 bits. Como en struct , el record se remonta a antes de que la programación orientada a objetos ingresara al lenguaje (era de Pascal).

Al igual que una estructura, un registro es también un trozo de memoria en línea, no un puntero a un trozo de memoria. Esto significa que cuando pasa un registro a una función, está pasando una copia, no un puntero / referencia. También significa que cuando declara una variable de tipo de registro en su código, se determina en el momento de la compilación qué tan grande es: las variables de tipo de registro utilizadas en una función se asignarán en la pila (no como un puntero en la pila, sino como un 4, 10, 16, etc estructura de bytes). Este tamaño fijo no juega bien con el polimorfismo.