Вопрос

Что -то, что я задавался долгое время: почему Delphi Records не может иметь наследство (и, следовательно, все другие важные функции ООП)?

По сути, это сделало бы записи, спланированную стеком версией классов, как классы C ++, и будет устанавливать «объекты» (примечание: не экземпляры) устаревшими. Я не вижу ничего проблемного с этим. Это также была бы хорошей возможностью для реализации форвардных деклараций для записей (которые я все еще сбил с толку относительно того, почему это все еще отсутствует).

Вы видите какие -нибудь проблемы с этим?

Это было полезно?

Решение

Вместе с этим вопросом, существует два вида наследования: наследование интерфейса и наследование реализации.

Наследство интерфейса обычно подразумевает полиморфизм. Это означает, что если B получено из A, то значения типа B могут храниться в местах типа A. Это проблематично для типов значений (например, записей), в отличие от эталонных типов, из -за нарезки. Если B больше, чем A, то хранение его в месте типа A усеет значение - любые поля, которые B, добавленные в его определение, вверх и выше, если они будут потеряны.

Наследство реализации с этой точки зрения является менее проблематичным. Если бы Дельфи имел учет наследования, но только в реализации, а не интерфейса, все было бы не очень плохо. Единственная проблема заключается в том, что простое изготовление значения поля типа A A A Type B делает большую часть того, что вы хотели бы от наследования реализации.

Другая проблема - виртуальные методы. Виртуальный метод для отправки требует какой-то типа тега для каждого значения, чтобы указать тип времени выполнения значения, чтобы можно было обнаружить правильный метод переопределения. Но у записей нет места для хранения этого типа: поля записи - это все поля, которые у него есть. Объекты (старый турбо -паскаль) могут иметь виртуальные методы, потому что они имеют VMT: первый объект в иерархии, чтобы определить виртуальный метод неявно добавляет VMT к концу определения объекта, выращивая его. Но объекты Turbo Pascal имеют одинаковую проблему нарезки, описанную выше, что делает их проблематичными. Виртуальные методы по типам значений эффективно требуют наследования интерфейса, что подразумевает проблему нарезки.

Таким образом, чтобы правильно поддерживать наследование интерфейса записи, нам понадобится какое -то решение проблемы нарезки. Бокс был бы одним из видов решения, но обычно требуется, чтобы сборы мусора можно было бы использовать, и оно внесло бы неоднозначность в язык, где может быть неясно, работаете ли вы со значением или ссылкой - немного похоже на целое число VS int в Java с автобоксингом. По крайней мере, в Java есть отдельные имена для штурмана против «видов» типов значений в штучной упаковке. Еще один способ сделать бокс - это то, что Google перейти с его интерфейсами, что является своего рода наследством интерфейса без наследования реализации, но требует определения интерфейсов отдельно, и все места интерфейса являются ссылками. Типы значений (например, записи) в штуке, если это упоминать ссылкой на интерфейс. И, конечно же, у Go также есть сборник мусора.

Другие советы

Записи и классы/объекты - две очень разные вещи в Delphi. По сути, запись Delphi - это C -структура - Delphi даже поддерживает синтаксис, чтобы сделать такие вещи, как есть запись, к которой можно получить доступ к 4 16 -битным целым числу, либо 23 -битным целым числом. Нравиться struct, record Датируется до того, как объектно -ориентированное программирование вошло в язык (эпоха Паскаля).

Как структура, запись также является встроенным куском памяти, а не указателем на кусок памяти. Это означает, что когда вы передаете запись в функцию, вы передаете копию, а не указатель/ссылку. Это также означает, что когда вы объявляете переменную типа записей в вашем коде, определяется во время компиляции, насколько она велика - переменные типа записи, используемые в функции, будут выделены в стеке (не как указатель на стеке, но как как 4, 10, 16 и т. Д. Структура байта). Этот фиксированный размер не хорошо играет с полиморфизмом.

Единственная проблема, которую я вижу (и я могу быть недальновидным или неправильным), - это цель. Записи предназначены для хранения данных, в то время как объекты предназначены для манипуляции и использования указанных данных. Почему шкафчик для хранения нужна процедуры манипуляции?

Вы правы, добавление наследства к записям, по сути, превратит их в классы C ++. И это ваш ответ прямо здесь: это не сделано, потому что это было бы ужасно. У вас могут быть типы значений с помощью стека, или у вас могут быть классы и объекты, но смешивание их-очень плохая идея. Как только вы это сделаете, вы получите всевозможные проблемы с управлением на протяжении всей жизни и в конечном итоге приходится создавать уродливые хаки, такие как Raii шаблона C ++, в язык, чтобы справиться с ними.

Итог: если вам нужен тип данных, который может быть унаследован и расширен, используйте классы. Вот для чего они там.

РЕДАКТИРОВАТЬ: В ответ на вопрос Cloud это на самом деле не то, что можно продемонстрировать с помощью одного простого примера. Вся модель объекта C ++ является катастрофой. Это может быть не похоже на то, что близко; Вы должны понять несколько взаимосвязанных проблем, чтобы по -настоящему понять общую картину. Райи - это просто беспорядок на вершине пирамиды. Может быть, я напишу более подробное объяснение в моем блоге позже на этой неделе, если у меня будет время.

Потому что у записей нет VMT (таблица виртуальных методов).

Вы можете попытаться использовать Delphi объект Ключевое слово для этого. Эти в основном наследуют, но ведут себя гораздо больше похожи на записи, чем на занятия.

Посмотри это нить и это описание.

В прошлые времена я использовал объекты (не классы!) В качестве записей с наследством.

В отличие от того, что некоторые люди здесь говорят, есть законные причины для этого. Случай, который я сделал, включал две структуры из внешних источников (API, а не что-то от диска-мне нужна полностью сформированная запись в памяти), вторая из которых просто расширила первую.

Такие случаи очень редки, хотя.

Это по теме к вашему вопросу и связано с расширением функциональности типов записи и классов через класс и записывающих помощников. Согласно документации Embarcadero по этому поводу, вы можете расширить класс или запись (но ни одна перегрузка оператора не поддерживается помощниками). Таким образом, в основном вы можете расширить функциональность с точки зрения методов членов, но без данных членов). Они поддерживают поля классов, которые вы можете получить через Getters и Setters обычным способом, хотя я не проверял это. Если вы хотите интерфейс доступ к данным класса или записи, к которым вы добавили помощника, вы, вероятно, могли бы достичь этого (т.е. запуска события или сигнала, когда были изменены данные члена исходного класса или записи). Вы не могли бы реализовать скрытые данные, но это позволяет вам переопределить существующую функцию члена исходного класса.

например. Этот пример работает в Delphi XE4. Создайте новое приложение VCL Forms и замените код из Unit1 на следующий код:

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;

Обратите внимание, что результат составляет 7,8102, а не 11. Это показывает, что вы можете скрыть методы члена исходного класса или записи с помощью класса или помощника записи.

Таким образом, в каком -то смысле вы просто относитесь к доступу к исходным членам данных так же, как и при изменении значений из единицы, в котором класс объявляется путем изменения через свойства, а не поля непосредственно, поэтому соответствующие действия предпринимаются Геттеры и сеттеры этих данных.

Спасибо, что задали вопрос. Я, конечно, многому научился, пытаясь найти ответ, и это тоже помогло мне.

Брайан Джозеф Джонс

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top