Pergunta

Algo que me pergunto há muito tempo:por que os registros Delphi não podem ter herança (e, portanto, todos os outros recursos importantes de OOP)?

Isso essencialmente tornaria os registros a versão das classes alocadas na pilha, assim como as classes C++, e renderizaria "objetos" (nota:não instâncias) obsoletas.Não vejo nada de problemático nisso.Esta também seria uma boa oportunidade para implementar declarações futuras para registros (que ainda não sei por que ainda estão faltando).

Você vê algum problema nisso?

Foi útil?

Solução

Relevante para esta questão, existem dois tipos de herança: herança da interface e herança de implementação.

A herança da interface geralmente implica polimorfismo. Isso significa que, se B deriva de A, os valores do tipo B poderão ser armazenados nos locais do tipo A., isso é problemático para tipos de valor (como registros), em oposição aos tipos de referência, devido ao fatiamento. Se B for maior que A, armazená -lo em um local do tipo A truncará o valor - quaisquer campos que B adicionados em sua definição além dos de A serão perdidos.

A herança de implementação é menos problemática dessa perspectiva. Se Delphi tivesse herança de registro, mas apenas da implementação, e não da interface, as coisas não seriam tão ruins. O único problema é que simplesmente criar um valor do tipo A do tipo B do Tipo B faz a maior parte do que você deseja da herança fora da implementação.

A outra questão são os métodos virtuais. A expedição de método virtual requer algum tipo de tag por valor para indicar o tipo de tempo de execução do valor, para que o método substituído correto possa ser descoberto. Mas os registros não têm lugar para armazenar esse tipo: os campos do registro são todos os campos que possui. Objetos (o tipo antigo Turbo Pascal) podem ter métodos virtuais porque têm um VMT: o primeiro objeto na hierarquia a definir um método virtual adiciona implicitamente um VMT ao final da definição de objeto, cultivando -o. Mas os objetos Turbo Pascal têm o mesmo problema de fatiamento descrito acima, o que os torna problemáticos. Os métodos virtuais nos tipos de valor requer efetivamente a herança da interface, o que implica o problema de fatiamento.

Portanto, para suportar adequadamente a herança de interface de registro corretamente, precisaríamos de algum tipo de solução para o problema de fatiamento. O boxe seria um tipo de solução, mas geralmente exige que a coleção de lixo seja utilizável e introduziria ambiguidade no idioma, onde pode não ficar claro se você está trabalhando com um valor ou uma referência - um pouco como inteiro vs int em java com auto -espinhamento. Pelo menos em Java, existem nomes separados para os "tipos" de valor em caixa vs não caixas. Outra maneira de fazer o boxe é como o Google ir com suas interfaces, que é um tipo de herança de interface sem herança de implementação, mas exige que as interfaces sejam definidas separadamente e todos os locais de interface são referências. Os tipos de valor (por exemplo, registros) são encaixotados quando referidos por uma referência de interface. E, claro, Go também tem coleta de lixo.

Outras dicas

Registros e Classes/Objetos são duas coisas muito diferentes no Delphi.Basicamente, um registro Delphi é uma estrutura C - o Delphi ainda suporta a sintaxe para fazer coisas como ter um registro que pode ser acessado como 4 inteiros de 16 bits ou 2 inteiros de 32 bits.Como struct, record remonta a antes da programação orientada a objetos entrar na linguagem (era Pascal).

Assim como uma estrutura, um registro também é um pedaço de memória embutido, não um ponteiro para um pedaço de memória.Isso significa que quando você passa um registro para uma função, você está passando uma cópia, não um ponteiro/referência.Isso também significa que quando você declara uma variável de tipo de registro em seu código, é determinado em tempo de compilação o tamanho dela - variáveis ​​de tipo de registro usadas em uma função serão alocadas na pilha (não como um ponteiro na pilha, mas como uma estrutura de 4, 10, 16, etc. bytes).Esse tamanho fixo não funciona bem com polimorfismo.

O único problema que vejo (e poderia ser míope ou errado) é um propósito. Os registros são para armazenar dados enquanto os objetos são para manipular e usar os referidos dados. Por que um armário de armazenamento precisa de rotinas de manipulação?

Você está certo, adicionar herança aos registros os transformaria essencialmente em classes C ++. E essa é a sua resposta ali: não é feito porque seria uma coisa horrível de se fazer. Você pode ter tipos de valor alocados em pilha, ou pode ter classes e objetos, mas misturar os dois é uma idéia muito ruim. Depois de fazer, você acaba com todos os tipos de problemas de gerenciamento da vida e acaba tendo que criar hacks feios como o padrão RAII do C ++ no idioma para lidar com eles.

Conclusão: se você deseja um tipo de dados que possa ser herdado e estendido, use classes. É para isso que eles estão lá.

EDIT: Em resposta à pergunta da nuvem, isso não é realmente algo que pode ser demonstrado por meio de um único exemplo simples. Todo o modelo de objeto C ++ é um desastre. Pode não parecer de perto; Você precisa entender vários problemas interconectados para realmente entender o quadro geral. Raii é apenas a bagunça no topo da pirâmide. Talvez eu escreva uma explicação mais detalhada no meu blog ainda esta semana, se eu tiver tempo.

Porque os registros não possuem VMT (tabela de método virtual).

Você pode tentar usar o delphi objeto palavra -chave para isso. Eles basicamente são herdáveis, mas se comportam muito mais como registros do que com as aulas.

Veja isso fio e isto Descrição.

Nos tempos anteriores, usei objetos (não aulas!) Como registros com herança.

Ao contrário do que algumas pessoas aqui estão dizendo, há razões legítimas para isso. O caso que fiz isso envolveu duas estruturas de fontes externas (API, nada fora do disco-eu precisava do registro totalmente formado na memória), o segundo dos quais apenas estendeu o primeiro.

Tais casos são muito raros, no entanto.

Isso está no tópico à sua pergunta e se relaciona a estender a funcionalidade dos tipos de registro e classe via classe e registro ajudantes. De acordo com a documentação da Embarcadero sobre isso, você pode estender uma classe ou registro (mas nenhuma sobrecarga do operador é suportada pelos ajudantes). Então, basicamente, você pode estender a funcionalidade em termos de métodos de membros, mas nenhum dado de membro). Eles suportam campos de classe que você pode acessar via getters e setters da maneira usual, embora eu não tenha testado isso. Se você deseja interface o acesso aos dados da classe ou registro para o qual estava adicionando o ajudante, provavelmente poderia conseguir isso (ou seja, acionando um evento ou sinal quando os dados do membro da classe ou registro original foram alterados). Você não pode implementar o esconderijo de dados, mas eles permitem substituir uma função de membro existente da classe original.

por exemplo. Este exemplo funciona em Delphi Xe4. Crie um novo aplicativo VCL Forms e substitua o código da unidade1 pelo seguinte 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 o resultado é 7.8102 em vez de 11. Isso mostra que você pode ocultar os métodos membros da classe ou registro original com uma classe ou auxiliar de registro.

Portanto, de certa forma, você apenas trataria o acesso aos membros originais dos dados da mesma forma que faria em mudar valores dentro da unidade em que uma classe é declarada mudando através das propriedades, em vez dos campos diretamente para que as ações apropriadas sejam tomadas por os getters e setters desses dados.

Obrigado por fazer a pergunta. Eu certamente aprendi muito ao tentar encontrar a resposta e isso também me ajudou muito.

Brian Joseph Johns

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top