Почему записи Delphi не могут наследоваться? - PullRequest
17 голосов
/ 19 января 2010

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

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

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

Ответы [ 8 ]

25 голосов
/ 20 января 2010

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

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

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

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

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

6 голосов
/ 19 января 2010

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

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

5 голосов
/ 20 января 2010

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

5 голосов
/ 19 января 2010

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

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

5 голосов
/ 19 января 2010

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

3 голосов
/ 20 января 2010

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

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

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

3 голосов
/ 20 января 2010

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

См. Эту тему и описание .

0 голосов
/ 07 октября 2014

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...