私は理解するを探しています
オブジェクトコンストラクターに適用される場合。コンパイラがシャットダウンするまで、ランダムにキーワードを追加するたびに(Delphiで12年間開発した後)、ランダムに試すのではなく、自分が何をしているのかを知りたいと思います。
架空のオブジェクトのセットが与えられた場合:
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); override;
end;
私が彼らに振る舞わせたい方法はおそらく宣言から明らかですが:
TComputer
には単純なコンストラクターがあり、子孫はそれをオーバーライドできますTCellPhone
には代替コンストラクターがあり、子孫はそれをオーバーライドできますTiPhone
は両方のコンストラクターをオーバーライドし、それぞれの継承バージョンを呼び出しますこれで、そのコードはコンパイルされません。 なぜ動作しないのか理解したい。また、コンストラクターをオーバーライドする適切な方法を理解したいと思います。または、コンストラクターをオーバーライドすることはできませんか?それとも、コンストラクターをオーバーライドすることは完全に受け入れられますか?おそらく、複数のコンストラクターを持つべきではありません。おそらく、複数のコンストラクターを持つことは完全に許容されます。
なぜなのか理解したい。それを修正することは明らかです。
編集:私はまた、virtual
、override
、overload
、reintroduce
。キーワードのすべての組み合わせを試すと、組み合わせの数が急増するためです。
編集2:「オブジェクト階層は可能ですか?」で始める必要があると思います。いいえ、なぜですか?たとえば、祖先からコンストラクターを持つことは根本的に間違っていますか?
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual;
end;
TCellPhone
には2つのコンストラクターがあると思います。しかし、Delphiでキーワードの組み合わせを見つけて、それが有効なことだと思わせることができません。ここTCellPhone
に2つのコンストラクターを持つことができると考えるのは根本的に間違っていますか?
注:質問に答えるために、この線より下のすべてが厳密に必要なわけではありませんが、私の考えを説明するのに役立ちます。おそらく、私の思考プロセスに基づいて、すべてを明確にするために私が欠けている基本的な部分を見ることができます。
現在、これらの宣言はコンパイルされません。
//Method Create hides virtual method of base type TComputer:
TCellPhone = class(TComputer)
constructor Create(Cup: Integer; Teapot: string); virtual;
//Method Create hides virtual method of base type TCellPhone:
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override;
constructor Create(Cup: Integer; Teapot: string); overload; <--------
end;
そこで、最初にTCellPhone
を修正してみます。 overload
キーワードをランダムに追加することから始めます(reintroduce
が必要ないことはわかっています。これは、必要のない他のコンストラクターを非表示にするためです)。
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;
しかし、それは失敗します:Field definition not allowed after methods or properties
。
メソッドまたはプロパティの後にフィールドがない場合でも、virtual
およびoverload
キーワードの順序を逆にすると、Delphiがシャットダウンすることを経験から知っています。
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); overload; virtual;
end;
しかし、それでもエラーが発生します:
メソッド「作成」は、基本タイプ「TComputer」の仮想メソッドを非表示にします
だから私は両方のキーワードを削除してみます:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
end;
しかし、それでもエラーが発生します:
メソッド「作成」は、基本タイプ「TComputer」の仮想メソッドを非表示にします
だから私は今reintroduce
を試すことに辞任します:
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;
そして今、TCellPhoneはコンパイルされますが、それはTiPhoneにとって事態をさらに悪化させました。
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); override; <-----cannot override a static method
constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method
end;
どちらもオーバーライドできないと不平を言っているので、override
キーワードを削除します。
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
constructor Create(Cup: Integer; Teapot: string);
end;
しかし、2番目の作成では、オーバーロードでマークする必要があると言われています(実際には、両方をオーバーロードとしてマークします。そうしないとどうなるかわかっているからです)。
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); overload;
constructor Create(Cup: Integer; Teapot: string); overload;
end;
interface
セクションではすべて問題ありません。残念ながら、私の実装は機能しません。 TiPhoneの単一パラメーターコンストラクターは、継承されたコンストラクターを呼び出すことができません。
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup); <---- Not enough actual parameters
end;
元の宣言セットが正しくコンパイルされない理由が2つあります。
TCellPhone
にwarningがあり、そのコンストラクターが基本クラスのメソッドを非表示にしているはずです。これは、基本クラスのメソッドがvirtualであり、コンパイラーが同じnewメソッドを導入することを心配しているためです。基本クラスのメソッドをオーバーライドせずに名前を付けます。署名が異なっていても構いません。基本クラスのメソッドを実際に非表示にすることが意図されている場合は、盲目的な推測の1つが示しているように、子孫宣言でreintroduce
を使用する必要があります。その指令の唯一の目的は、警告を鎮めることです。実行時の動作には影響しません。
後でTIPhone
で何が起こるかを無視すると、次のTCellPhone
宣言が必要になります。祖先メソッドを非表示にしますが、仮想にする必要もあります。祖先メソッドは2つの完全に別個のメソッドであり、たまたま同じ名前であるため、祖先メソッドの仮想性は継承されません。したがって、新しい宣言でもvirtual
を使用する必要があります。
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
end;
基本クラスのコンストラクターTComputer.Create
も、itsの祖先TObject.Create
のメソッドを非表示にしていますが、これはTObject
のメソッド以降です。は仮想ではなく、コンパイラはそれについて警告しません。非仮想メソッドの非表示は常に発生し、通常は目立たないものです。
オーバーライドする1つの引数のコンストラクターがなくなったため、TIPhone
でerrorが発生するはずです。 TCellPhone
に隠しました。 2つのコンストラクターが必要なため、reintroduce
は明らかに以前に使用する正しい選択ではありませんでした。基本クラスのコンストラクターを非表示にしたくありません。別のコンストラクターでそれを拡張したい。
両方のコンストラクターに同じ名前を付ける必要があるため、overload
ディレクティブを使用する必要があります。そのディレクティブはで使用する必要があります すべての元の宣言—それぞれの個別の署名が初めて導入されたとき 子孫での後続の宣言。 all宣言(基本クラスも含む)で必要だと思いました。それでも問題はありませんが、必須ではないと思います。したがって、宣言は次のようになります。
TComputer = class(TObject)
public
constructor Create(Cup: Integer);
overload; // Allow descendants to add more constructors named Create.
virtual; // Allow descendants to re-implement this constructor.
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string);
overload; // Add another method named Create.
virtual; // Allow descendants to re-implement this constructor.
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer);
override; // Re-implement the ancestor's Create(Integer).
constructor Create(Cup: Integer; Teapot: string);
override; // Re-implement the ancestor's Create(Integer, string).
end;
最新のドキュメント すべてがどの順序で進むべきかを示します:
reintroduce; 過負荷; バインディング; 呼び出し規約; abstract; 警告
ここで、bindingはvirtual、dynamic、またはoverride; 呼び出し規約はregister、Pascal、cdecl、stdcall、またはsafecall;およびwarningisplatform、deprecated、またはlibrary。
これらは6つの異なるカテゴリですが、私の経験では、宣言に3つを超えることはめったにありません。 (たとえば、呼び出し規約を指定する必要がある関数は、おそらくメソッドではないため、仮想化することはできません。)順序を覚えていません。私はそれが今日まで文書化されているのを見たことがありません。代わりに、各ディレクティブの目的を覚えておくと便利だと思います。さまざまなタスクに必要なディレクティブを覚えていると、最終的には2つか3つになり、有効な順序を取得するための実験は非常に簡単です。コンパイラは複数の順序を受け入れる場合がありますが、心配しないでください。順序は意味を決定する上で重要ではありません。コンパイラが受け入れる順序は、他の順序と同じ意味になります(呼び出し規約を除いて、複数の規則に言及する場合は、最後の1つだけが重要なので、そうしないでください)。
したがって、各ディレクティブの目的を覚えて、どのディレクティブが一緒に意味をなさないかを考える必要があります。たとえば、reintroduce
とoverride
は意味が逆であるため、同時に使用することはできません。また、virtual
とoverride
を一緒に使用することはできません。これは、一方が他方を暗示しているためです。
たくさんのディレクティブが山積みになっている場合は、必要な残りのディレクティブを作成している間、いつでも画像からoverload
を切り取ることができます。メソッドに異なる名前を付け、otherディレクティブのどれが必要かを判断し、overload
を追加して、すべて同じ名前を付け直します。
私はDelphi5を持っていないので、最新バージョンのDelphiXEに基づいて回答していることに注意してください。ここでそれが実際に違いを生むとは思わないが、もしそうなら、あなたは警告されている。 :)
これは主に http://docwiki.embarcadero.com/RADStudio/en/Methods に基づいています。これは、メソッドの動作に関する現在のドキュメントです。 Delphi5のヘルプファイルにもおそらくこれに似たものがあります。
まず、仮想コンストラクターはここではあまり意味がないかもしれません。これが必要な場合がいくつかありますが、おそらくこれは1つではありません。仮想コンストラクターが必要な状況については、 http://docwiki.embarcadero.com/RADStudio/en/Class_References を参照してください。ただし、コーディング時にオブジェクトのタイプを常に知っている場合は、 、あなたはしません。
1パラメーターコンストラクターで発生する問題は、親クラスに1パラメーターコンストラクター自体がないことです-継承されたコンストラクターは公開されません。 inherited
を使用して階層内の複数のレベルを上に移動することはできません。呼び出しできるのは、直接の親のみです。デフォルト値を使用して2パラメーターコンストラクターを呼び出すか、TCellPhoneに1パラメーターコンストラクターを追加する必要があります。
一般に、4つのキーワードには次の意味があります。
virtual
-これをランタイムディスパッチが必要な関数としてマークします(ポリモーフィックな動作を許可します)。これは初期定義のみであり、サブクラスでオーバーライドする場合ではありません。override
-仮想メソッドの新しい実装を提供します。overload
-別の関数と同じ名前で、パラメーターリストが異なる関数をマークします。reintroduce
-コンパイラに、単にoverride
を指定するのを忘れるのではなく、実際に意図した仮想メソッドを非表示にするように指示します。必要な順序については、ドキュメントに詳しく説明されています。
メソッド宣言には、他の関数やプロシージャでは使用されない特別なディレクティブを含めることができます。ディレクティブは、定義宣言ではなく、クラス宣言にのみ表示する必要があり、常に次の順序でリストする必要があります。
再導入;過負荷;製本;呼び出し規約;概要;警告
バインディングが仮想、動的、またはオーバーライドである場合。呼び出し規約は、register、Pascal、cdecl、stdcall、またはsafecallです。警告はプラットフォーム、非推奨、またはライブラリです。
これは、必要な定義の実用的な実装です。
program OnConstructors;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TComputer = class(TObject)
public
constructor Create(Cup: Integer); virtual;
end;
TCellPhone = class(TComputer)
public
constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;
TiPhone = class(TCellPhone)
public
constructor Create(Cup: Integer); overload; override;
constructor Create(Cup: Integer; Teapot: string); override;
end;
{ TComputer }
constructor TComputer.Create(Cup: Integer);
begin
Writeln('Computer: cup = ', Cup);
end;
{ TCellPhone }
constructor TCellPhone.Create(Cup: Integer; Teapot: string);
begin
inherited Create(Cup);
Writeln('Cellphone: teapot = ', Teapot);
end;
{ TiPhone }
constructor TiPhone.Create(Cup: Integer);
begin
inherited Create(Cup);
Writeln('iPhone: cup = ', Cup);
end;
constructor TiPhone.Create(Cup: Integer; Teapot: string);
begin
inherited;
Writeln('iPhone: teapot = ', Teapot);
end;
var
C: TComputer;
begin
C := TComputer.Create(1);
Writeln; FreeAndNil(C);
C := TCellPhone.Create(2);
Writeln; FreeAndNil(C);
C := TCellPhone.Create(3, 'kettle');
Writeln; FreeAndNil(C);
C := TiPhone.Create(4);
Writeln; FreeAndNil(C);
C := TiPhone.Create(5, 'iPot');
Readln; FreeAndNil(C);
end.
結果:
Computer: cup = 1
Computer: cup = 2
Computer: cup = 3
Cellphone: teapot = kettle
Computer: cup = 4
iPhone: cup = 4
Computer: cup = 5
Cellphone: teapot = iPot
iPhone: teapot = iPot
最初の部分は this に準拠しています。次に、TiPhone
2つのコンストラクターの定義は次のように進行します。
overload; override
を使用してTCellPhone
をオーバーロードし、他のコンストラクターをオーバーライドします。override
を必要とします。両方にオーバーロードを使用します。それが私のやり方であり、機能します。
constructor Create; Overload
; <-ここでオーバーロードを使用
constructor Values; Overload;
<-そしてここ
2つの異なるコンストラクターに同じ名前を使用しないことを忘れないでください