なぜDelphi Recordsには相続ができないのですか?
-
21-09-2019 - |
質問
私が長い間疑問に思っていたもの:なぜDelphi Recordsは相続財産(したがって他のすべての重要なOOP機能)を持つことができないのですか?
これにより、基本的にC ++クラスと同様に、スタックに割り当てられたクラスのバージョンが記録され、「オブジェクト」(注:インスタンスではない)が廃止されます。問題のあるものは何もありません。これはまた、レコードのフォワード宣言を実装する良い機会になるでしょう(私はまだそれがまだ欠けているのかについてまだ困惑しています)。
これに問題がありますか?
解決
この質問に関連して、インターフェイスの継承と実装継承の2種類の継承があります。
インターフェイス継承は一般に多型を意味します。つまり、BがAから派生した場合、タイプBの値はタイプAの位置に保存できます。これは、スライスのために参照タイプとは対照的に、値タイプ(レコードなど)で問題があります。 bがAよりも大きい場合、タイプAの場所に保存すると、値が切り捨てられます。Bbが定義に追加されたフィールドは、Aの上にあるフィールドは失われます。
実装の継承は、この観点からは問題が少ない。 Delphiが記録的な継承を持っていたが、インターフェースのものではなく実装のみを持っていた場合、事態はそれほど悪くないでしょう。唯一の問題は、タイプAのタイプAの値をタイプBのフィールドに作成するだけで、実装の継承から必要なもののほとんどを実行することです。
もう1つの問題は仮想方法です。仮想メソッドディスパッチには、正しいオーバーライドされたメソッドを発見できるように、値のランタイムタイプを示すために、何らかの価値ごとのタグが必要です。しかし、レコードにはこのタイプを保存する場所はありません。レコードのフィールドは、すべてのフィールドです。オブジェクト(古いターボパスカルの種類)には、VMT:階層内の最初のオブジェクトがあり、仮想メソッドを定義する最初のオブジェクトがオブジェクト定義の最後にVMTを暗黙的に追加し、成長させます。しかし、ターボパスカルオブジェクトには、上記の同じスライス問題があるため、問題が発生します。値タイプに関する仮想メソッドは、インターフェイスの継承を効果的に必要とします。これは、スライスの問題を意味します。
したがって、レコードインターフェイスの継承を適切に適切にサポートするために、スライス問題に対する何らかの解決策が必要になります。ボクシングは1つの種類の解決策ですが、一般的にごみ収集を使用できるようにする必要があり、言語に曖昧さをもたらします。ここでは、価値を使用しているのか、参照を使用しているのかは明らかではありません。自動ボクシング付きJavaのINT。少なくともJavaには、箱入りの箱入りの「種類」のバリュータイプに個別の名前があります。ボクシングを行う別の方法は、Googleがインターフェイスを使用したようなものです。これは、実装継承のない一種のインターフェイス継承ですが、インターフェイスを個別に定義する必要があり、すべてのインターフェイスの位置は参照です。値タイプ(レコードなど)は、インターフェイスリファレンスで参照された場合にボックス化されます。そしてもちろん、Goにはゴミコレクションもあります。
他のヒント
レコードとクラス/オブジェクトは、Delphiの2つの非常に異なるものです。基本的にDelphiレコードはC構造体です-Delphiは構文をサポートして、46ビット整数または2 32ビット整数のいずれかとしてアクセスできるレコードを持つようなことを行うことさえします。お気に入り struct
, record
オブジェクト指向プログラミングが言語に入る前にさかのぼります(Pascal Era)。
構造体のように、レコードはメモリのインラインチャンクであり、メモリの塊へのポインターではありません。これは、レコードを関数に渡すと、ポインター/参照ではなくコピーを渡すことを意味します。また、コードでレコードタイプ変数を宣言すると、コンパイル時にそれがどれだけ大きいかを決定することを意味します - 関数で使用されるレコードタイプ変数はスタックで割り当てられます(スタックのポインターとしてではなく、 4、10、16、などのバイト構造)。この固定サイズは、多型とうまく機能しません。
私が見ている唯一の問題(そして、私は近視眼的または間違っている可能性があります)は目的です。レコードはデータを保存するためのものであり、オブジェクトは上記のデータを操作して使用するためのものです。ストレージロッカーに操作ルーチンが必要なのはなぜですか?
あなたは正しいです、レコードに継承を追加すると、本質的にそれらをC ++クラスに変えるでしょう。そして、それはすぐそこにあなたの答えです:それは恐ろしいことだからです。 Stack-Allocated Valueタイプを使用することも、クラスやオブジェクトを作成することもできますが、2つを混ぜることは非常に悪い考えです。やると、あらゆる種類の生涯管理の問題が発生し、C ++のRaiiパターンのような醜いハックを言語に取り入れるために、ugいハックを構築する必要があります。
結論:継承して拡張できるデータ型が必要な場合は、クラスを使用してください。それが彼らがそこにいるものです。
編集:Cloudの質問に応えて、これは単一の簡単な例で実証できるものではありません。 C ++オブジェクトモデル全体は災害です。近くには見えないかもしれません。大きな絵を本当に把握するには、いくつかの相互接続された問題を理解する必要があります。 Raiiは、ピラミッドの頂上にある混乱です。時間があれば、今週後半に私のブログにもっと詳細な説明を書くかもしれません。
レコードにはVMT(仮想メソッドテーブル)がないためです。
過去には、継承の記録としてオブジェクト(クラスではなく!)を使用しました。
ここにいる一部の人々が、これには正当な理由があると言っているものとは異なります。私がやった場合、外部ソースからの2つの構造(API、ディスクの外ではありません。メモリに完全に形成されたレコードが必要でした)が含まれ、その2番目は最初のものを単に拡張しました。
ただし、そのような場合は非常にまれです。
これはあなたの質問のトピックに関するものであり、クラスとレコードのヘルパーを介してレコードタイプとクラスタイプの機能を拡張することに関連しています。これに関するEmbarcaderoのドキュメントによると、クラスまたはレコードを拡張できます(ただし、ヘルパーによってオペレーターの過負荷はサポートされていません)。したがって、基本的には、メンバーメソッドの観点から機能を拡張できますが、メンバーデータはありません)。私はこれをテストしていませんが、通常の方法でゲッターとセッターを介してアクセスできるクラスフィールドをサポートしています。ヘルパーを追加したクラスまたはレコードのデータへのアクセスをインターフェイスしたい場合は、おそらくこれを達成できます(つまり、元のクラスまたはレコードのメンバーデータが変更されたときにイベントまたは信号をトリガーします)。ただし、データを隠すことはできませんでしたが、元のクラスの既存のメンバー関数をオーバーライドすることができます。
例えば。この例は、Delphi XE4で動作します。新しいVCLフォームアプリケーションを作成し、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;
結果は11ではなく7.8102であることに注意してください。これは、クラスまたはレコードヘルパーで元のクラスまたはレコードのメンバーメソッドを隠すことができることを示しています。
したがって、ある意味では、フィールドではなくプロパティを変更することによってクラスが宣言されるユニット内から値を変更するのと同じように、元のデータメンバーへのアクセスを扱うだけで、適切なアクションが実行されます。そのデータのゲッターとセッター。
質問をしてくれてありがとう。私は確かに答えを見つけようとして多くのことを学びました、そしてそれは私も大いに助けました。
ブライアン・ジョセフ・ジョンズ