2010-01-19 24 views
16

Algo que me he preguntado durante mucho tiempo: ¿por qué los registros de Delphi no pueden tener herencia (y por lo tanto todas las demás características importantes de OOP)?¿Por qué los registros de Delphi no tienen herencia?

Esto esencialmente haría registros de la versión de clases asignada por la pila, al igual que las clases de C++, y dejaría obsoletos los "objetos" (nota: no instancias). No veo nada problemático con eso. Esta también sería una buena oportunidad para implementar declaraciones futuras para los registros (que todavía estoy desconcertado sobre por qué aún falta).

¿Ve algún problema con esto?

+2

Cuando dices * protoyping *, creo que el término que realmente quieres decir es * forward declarations *. –

+0

Sí, lo siento, lapso de memoria momentáneo. : P Básicamente es lo mismo, simplemente deletreado de manera diferente entre el mundo Delphi y C++. Aunque prefiero el nombre Delphi, mucho más autoexplicativo. – Cloud737

+1

En realidad, * prototype * solo se usa con respecto a funciones en C++, también. C++ usa * forward declaration * para tipos de clase, al igual que Delphi. –

Respuesta

24

Pertinente a esta pregunta, existen dos tipos de herencia: la herencia de la interfaz y la herencia de la implementación.

La herencia de la interfaz generalmente implica polimorfismo. Significa que si B se deriva de A, entonces B se puede almacenar en ubicaciones de tipo A. Esto es problemático para los tipos de valores (como los registros) en comparación con los tipos de referencia, debido a la división. Si B es más grande que A, entonces almacenarlo en una ubicación de tipo A truncará el valor; cualquier campo que B agregue en su definición por encima de A se perderá.

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

El otro problema son los métodos virtuales. El envío de métodos virtuales 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 correcto anulado. 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 de 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 descrito anteriormente, lo que los hace problemáticos. Los métodos virtuales en los tipos de valor requieren de manera efectiva la herencia de la interfaz, lo que implica el problema de corte.

Para admitir adecuadamente la herencia de interfaz de grabación correctamente, necesitaríamos algún tipo de solución para el problema de corte. El boxeo sería un tipo de solución, pero generalmente requiere la recolección de basura para ser utilizable, e introduciría ambigüedad en el lenguaje, donde puede no estar 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 valores en caja o no. 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 la interfaz son referencias. Los tipos de valor (por ejemplo, registros) están encuadrados cuando se hace referencia a ellos mediante una referencia de interfaz. Y, por supuesto, Go también tiene recolección de basura.

+2

+1 muy interesante, lea – jpfollenius

+0

Ahora veo, así que básicamente el problema es principalmente con el corte. Pero, ¿el boxeo realmente no haría registros exactamente como las clases, ya que cada variable sería en realidad una referencia al objeto real? ¿O sería ese objeto de alguna manera realmente asignado por la pila? Por cierto, es un honor tenerlo respondiendo mi pregunta. Me trae una sonrisa a la cara para que un revelador famoso responda. ¡Muchas gracias! :) – Cloud737

+1

Los registros en caja serían, eh, referenciados por referencia; donde se almacena el valor, ya sea en el montón o en la pila, hay un detalle de implementación que depende, p. análisis de escape (la ubicación de la pila sobrevive a la referencia). Pero no necesariamente serían solo como clases; si los tipos boxed versus no boxed tenían diferentes nombres o sintaxis, o eran como Go y usaban herencia solo de interfaz, entonces se podía imaginar polimorfismo para los tipos encuadrados (cuando se hace referencia por referencia) pero no para las ubicaciones directamente (por lo tanto evitando rebanar). Un poco como int/entero de Java, IOW. –

5

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

+0

Sí, pero Delphi ya tiene "registros con métodos", por lo que esencialmente ahora también pueden manipular datos. ¿Por qué no ir todo el camino? De cualquier manera, ¿por qué no tener objetos asignados a la pila, cualquiera que sea su nombre? De esta manera, la gente dejará de quejarse por la falta de recolección de basura y todo será maravilloso. – Cloud737

+4

Los métodos que puede agregar a los registros en Delphi son solo azúcar sintáctica para métodos estáticos, al igual que los métodos de extensión en C#. No hay espacio para el polimorfismo, ya que no se usa una tabla de métodos virtuales. – David

+0

@ Cloud737: ciertamente no sería maravilloso, siempre que Delphi no permita que se declaren las variables cuando se usan por primera vez. – mghie

6

Registros y Clases/Objetos son dos cosas muy diferentes en Delphi. Básicamente, un registro Delphi es una estructura C: Delphi incluso admite la sintaxis para hacer cosas como tener un registro al que se pueda acceder como 4 enteros de 16 bits o 2 enteros de 32 bits. Como struct, 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 también es un fragmento 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, en tiempo de compilación se determina 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 una estructura de bytes de 4, 10, 16, etc.). Este tamaño fijo no funciona bien con polimorfismo.

+0

Sé que los registros son equivalentes a las estructuras, pero aun así ... eso no excluye necesariamente expandirlos un poco. No estoy muy seguro, pero creo que es posible tener clases variantes también, por lo que los registros de variante ya que las clases no representarían un gran problema. Sé que los registros se asignan en la pila frente a las clases que se asignan al heap, pero las clases de C++ también se asignan en pila, así que no veo mucho problema aquí – Cloud737

+1

Los tamaños fijos funcionan bien con el polimorfismo. Tanto Turbo Pascal como C++ tienen clases de valor fijo de tipo fijo que todavía admiten la herencia y las funciones virtuales. –

+0

@Rob Kennedy: ¿Te refieres a tipos de "objetos"? Todavía están presentes en Delphi, solo eso no tienen sobrecarga de operador, funciones virtuales (no estoy seguro) y tienen algunos problemas con las propiedades o cualquier cosa que se les haya presentado. – Cloud737

5

Tiene razón, agregar herencia a los registros esencialmente los convertiría en clases de C++. Y esa es su respuesta: no está hecha porque sería algo horrible de hacer. Puede tener tipos de valores asignados a la pila, o puede tener clases y objetos, pero mezclarlos es una muy mala idea. Una vez que lo haces, terminas con todo tipo de problemas de administración de por vida y terminas teniendo que crear feos hacks como el patrón RAII de C++ en el lenguaje para poder lidiar con ellos.

En pocas palabras: si desea un tipo de datos que pueda heredarse y extenderse, utilice clases. Para eso están allí.

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 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 desastre en la parte superior de la pirámide. Tal vez escriba una explicación más detallada en mi blog más adelante esta semana, si tengo tiempo.

+0

No estoy muy familiarizado con el problema al que se refiere C++. No he mirado RAII demasiado. ¿Podría publicar un ejemplo de por qué sería un problema, por favor? – Cloud737

+5

-1 por la idea errónea de que RAII es un hack necesario pero feo. – mghie

+0

¿Cómo es un error, mghie? RAII es una de las peores características de lenguaje que he visto en cualquier lenguaje de programación, y estoy francamente desconcertado por la cantidad de codificadores de C++ que parecen pensar que de alguna manera es algo bueno. –

4

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

3

Puede intentar utilizar la palabra clave del objeto para eso. Esos son básicamente heredables, pero se comportan mucho más como registros que como clases.

Consulte este thread y este description.

+1

¡Ahhh! Nooo! ¡No use la palabra clave object! Ha estado en desuso por años y, a partir de D2010, ni siquiera fue realmente compatible. –

+0

hasta que finalmente se resuelva el problema de corte :-) –

3

En el pasado, he usado objetos (¡no clases!) Como registros con herencia.

A diferencia de lo que algunas personas aquí dicen que hay razones legítimas para esto.El caso en el que lo hice involucraba dos estructuras de fuentes externas (API, nada fuera del disco; necesitaba el registro completamente formado en la memoria), el segundo de los cuales simplemente extendía el primero.

Sin embargo, estos casos son muy raros.

0

Esto está relacionado con su pregunta y se relaciona con la extensión de la funcionalidad de los tipos de registro y clase a través de clases y asistentes de registro. De acuerdo con la documentación de Embarcadero, puede ampliar una clase o grabar (pero los helpers no soportan sobrecarga de operadores). Así que, básicamente, puede ampliar la funcionalidad en términos de métodos miembro pero no datos de miembros). Admiten campos de clase a los que se puede acceder a través de getters y setters de la manera habitual, aunque no he probado esto. Si quisiera acceder a la interfaz de los datos de la clase o registro al que estaba agregando el helper, probablemente podría lograr esto (es decir, activar un evento o señal cuando se modifiquen los datos de miembro de la clase o registro original). Sin embargo, no podría implementar la ocultación de datos pero le permite anular una función miembro existente de la clase original.

por ejemplo. Este ejemplo funciona en Delphi XE4. Crear un nuevo VCL formularios de solicitud y reemplazar 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; 

en cuenta que el resultado es 7,8102 en lugar de 11. Esto demuestra que se puede ocultar los métodos de miembros de la clase original o registro con una clase o asistente de grabación. Así, de alguna manera, trataría el acceso a los miembros de datos originales de la misma forma que cambiaría los valores dentro de la unidad en la que se declara una clase cambiando las propiedades en lugar de los campos directamente para que el las acciones son tomadas por los getters y setters de esa información.

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

Brian Joseph Johns

Cuestiones relacionadas