Почему у Delphi records нет наследства?

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

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

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

8 ответов


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

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

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

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

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


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

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


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


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

итог: Если вы хотите тип данных, который может быть унаследован и расширен, используйте классы. Для этого они и существуют.

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


потому что записи не имеют VMT (таблица виртуального метода).


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

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


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

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

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


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

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

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

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