40

私は理解しようとしています

  • バーチャル
  • オーバーライド
  • 過負荷
  • 再導入

オブジェクトコンストラクターに適用された場合。コンパイラがシャットダウンするまで、ランダムにキーワードを追加するたびに(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います。キーワードのすべての組み合わせを試すと、組み合わせの数が爆発的に増えるためです。overrideoverloadreintroduce

  • バーチャル; 過負荷;
  • バーチャル; オーバーライド;
  • オーバーライド; 過負荷;
  • オーバーライド; バーチャル;
  • バーチャル; オーバーライド; 過負荷;
  • バーチャル; 過負荷; オーバーライド;
  • 過負荷; バーチャル; オーバーライド;
  • オーバーライド; バーチャル; 過負荷;
  • オーバーライド; 過負荷; バーチャル;
  • 過負荷; オーバーライド; バーチャル;

編集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でキーワードの組み合わせを見つけて、それが有効なことだと思わせることができません。私はここに2つのコンストラクターを持つことができると考えるのは根本的に間違っていTCellPhoneますか?


注:この線より下のすべてが質問に答えるために厳密に必要なわけではありませんが、それは私の考えを説明するのに役立ちます。おそらく、私の思考プロセスに基づいて、すべてを明確にするために私が欠けている基本的な部分を見ることができます。

現在、これらの宣言はコンパイルされません。

//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

メソッドまたはプロパティの後にフィールドがない場合でも、キーワードvirtualoverloadキーワードの順序を逆にすると、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;
4

4 に答える 4

18

元の宣言セットが正しくコンパイルされない理由が2つあります。

  1. そのコンストラクタが基本クラスのメソッドを非表示にするという警告があるはずです。これは、基本クラスのメソッドが仮想であり、コンパイラーが、基本クラスのメソッドをオーバーライドせずに同じ名前の新しいメソッドを導入することを心配しているためです。署名が異なっていても構いません。基本クラスのメソッドを実際に非表示にすることが意図されている場合は、盲目的な推測の1つが示しているように、子孫宣言で使用する必要があります。その指令の唯一の目的は、警告を鎮めることです。実行時の動作には影響しません。TCellPhonereintroduce

    後で何が起こるかを無視するTIPhoneと、次のTCellPhone宣言が必要になります。祖先メソッドを非表示にしますが、仮想にする必要もあります。祖先メソッドは、たまたま同じ名前を持つ2つの完全に別個のメソッドであるため、仮想性を継承しません。virtualしたがって、新しい宣言でも使用する必要があります。

    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
    end;
    

    基本クラスのコンストラクター、TComputer.Createその祖先のメソッドを隠していますTObject.Createが、のメソッドTObjectは仮想ではないため、コンパイラーはそれについて警告しません。非仮想メソッドの非表示は常に発生し、通常は目立たないものです。

  2. オーバーライドする1つの引数のコンストラクターがなくなったため、でエラーが発生するはずです。TIPhoneあなたはそれをに隠しましたTCellPhone。2つのコンストラクターが必要なため、以前はreintroduce明らかに正しい選択ではありませんでした基本クラスのコンストラクターを非表示にしたくありません。別のコンストラクターで拡張したい。

    両方のコンストラクターに同じ名前を付けたいので、overloadディレクティブを使用する必要があります。そのディレクティブは、すべての元の宣言で使用する必要があります。最初に、それぞれの個別の署名が子孫の後続の宣言に導入されます。すべての宣言(基本クラスも含む)で必須だと思っていたので、それでも問題はありませんが、必須ではないと思います。したがって、宣言は次のようになります。

    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;
    

最新のドキュメントは、すべてがどのような順序で行われるべきかを示しています。

再導入; 過負荷; バインディング; 呼び出し規約; 抽象; 警告

ここで、バインディング仮想動的、またはオーバーライドです。呼び出し規約は、 registerpascalcdeclstdcall、またはsafecallです。警告は、プラットフォーム非推奨、またはライブラリです。

これらは6つの異なるカテゴリですが、私の経験では、宣言に3つを超えることはめったにありません。(たとえば、呼び出し規約を指定する必要がある関数は、おそらくメソッドではないため、仮想化することはできません。)順序を覚えていません。私はそれが今日まで文書化されているのを見たことがありません。代わりに、各ディレクティブの目的を覚えておくと便利だと思います。さまざまなタスクに必要なディレクティブを覚えていると、最終的には2つか3つになります。そうすれば、有効な順序を取得するための実験は非常に簡単です。コンパイラは複数の順序を受け入れる場合がありますが、心配しないでください。順序は意味を決定する上で重要ではありません。コンパイラが受け入れる順序は、他の順序と同じ意味になります(呼び出し規約を除きます。規則を複数指定する場合は、最後の1つだけが重要なので、そうしないでください)。

したがって、各ディレクティブの目的を覚えて、どのディレクティブが一緒に意味をなさないかを考える必要があります。たとえば、意味が逆であるため、同時に使用することはできませreintroduceoverride。そして、一方が他方を暗示しているためvirtual、一緒に使用することはできません。override

overloadたくさんのディレクティブが山積みになっている場合は、必要な残りのディレクティブを作成している間、いつでも画像を切り取ることができます。メソッドに異なる名前を付け、他のディレクティブのどれがそれ自体で必要overloadかを判断してから、すべて同じ名前を付け直しながら追加し直します。

于 2010-10-06T21:37:42.057 に答える
6

私は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です。警告は、プラットフォーム、非推奨、またはライブラリです。

于 2010-10-06T19:43:29.903 に答える
3

これは、必要な定義の実用的な実装です。

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

最初の部分はこれに準拠しています。次に、2つのコンストラクターの定義は次のTiPhoneように進行します。

  • 最初のコンストラクターは、継承された2つのコンストラクターの1つをオーバーロードし、その兄弟をオーバーライドします。これを実現するには、を使用overload; overrideして一方をオーバーロードし、TCellPhoneもう一方のコンストラクターをオーバーライドします。
  • それが行われると、2番目のコンストラクターはoverrideその兄弟をオーバーライドするための単純なものが必要です。
于 2010-10-06T22:46:26.967 に答える
0

両方にオーバーロードを使用します。それは私が行う方法であり、機能します。

constructor Create; Overload; <-ここでオーバーロードを使用

constructor Values; Overload;<-そしてここ

2つの異なるコンストラクターに同じ名前を使用しないことを忘れないでください

于 2016-08-19T20:48:42.883 に答える