web-dev-qa-db-ja.com

Delphi:コンストラクターを理解する

私は理解するを探しています

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

オブジェクトコンストラクターに適用される場合。コンパイラがシャットダウンするまで、ランダムにキーワードを追加するたびに(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は両方のコンストラクターをオーバーライドし、それぞれの継承バージョンを呼び出します

これで、そのコードはコンパイルされません。 なぜ動作しないのか理解したい。また、コンストラクターをオーバーライドする適切な方法を理解したいと思います。または、コンストラクターをオーバーライドすることはできませんか?それとも、コンストラクターをオーバーライドすることは完全に受け入れられますか?おそらく、複数のコンストラクターを持つべきではありません。おそらく、複数のコンストラクターを持つことは完全に許容されます。

なぜなのか理解したい。それを修正することは明らかです。

も参照してください

編集:私はまた、virtualoverrideoverloadreintroduce。キーワードのすべての組み合わせを試すと、組み合わせの数が急増するためです。

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

編集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;
34
Ian Boyd

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

  1. TCellPhonewarningがあり、そのコンストラクターが基本クラスのメソッドを非表示にしているはずです。これは、基本クラスのメソッドが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のメソッド以降です。は仮想ではなく、コンパイラはそれについて警告しません。非仮想メソッドの非表示は常に発生し、通常は目立たないものです。

  2. オーバーライドする1つの引数のコンストラクターがなくなったため、TIPhoneerrorが発生するはずです。 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; 警告

ここで、bindingvirtualdynamic、またはoverride; 呼び出し規約registerPascalcdeclstdcall、またはsafecall;およびwarningisplatformdeprecated、またはlibrary

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

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

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

16
Rob Kennedy

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

5
Michael Madsen

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

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 に準拠しています。次に、TiPhone2つのコンストラクターの定義は次のように進行します。

  • 最初のコンストラクターは、継承された2つのコンストラクターの1つ1つをオーバーロードし、その兄弟をオーバーライドします。これを実現するには、overload; overrideを使用してTCellPhoneをオーバーロードし、他のコンストラクターをオーバーライドします。
  • それが行われると、2番目のコンストラクターはその兄弟をオーバーライドするために単純なoverrideを必要とします。
2

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

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

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

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

0
Damon