Подтвердить что ты не робот

Почему данные Delphi не имеют наследства?

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

Вы можете попробовать использовать ключевое слово для Delphi. В основном они наследуются, но ведут себя скорее как записи, чем классы.

Смотрите thread, и это description.

Ответ 7

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

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

Такие случаи очень редки.

Ответ 8

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

например. Этот пример работает в 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. Это показывает, что вы можете скрыть методы-члены исходного класса или записи с помощью помощника класса или записи.

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

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

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