1

基本クラスと派生クラスの 2 つのクラスがあります。基本クラスは、パラメーターを持つ仮想メソッドを定義します。

function ToName(MsgIfNil:string=''); virtual;

派生クラスはメソッドを再定義します。

function ToName(MsgIfNil:string=''); reintroduce;

両方のメソッドの実装は、次のコードに似ています。

function TBaseClass.ToName(MsgIfNil:string)
begin
  if (Self=nil) then
    Result := MsgIfNil
  else
    Result := Self.SomeProperty;
end;

問題は次のとおりです。

1) 派生クラスにメソッドを再導入せず、通常のオーバーライド キーワードを使用すると、このメソッドを呼び出すとアクセス違反が発生する

2) nil であるオブジェクトからメソッドを呼び出し、オブジェクトの推定クラスが TBaseObject である場合、ベース仮想メソッドを呼び出す代わりにクラッシュ (AV) します。

メソッドでパラメーターが定義されていない場合は、AV なしで正しいメソッドが呼び出されます。派生クラスのメソッドがオーバーライドされていても問題なく動作します。

上記のソリューションは、TBaseClass から派生した任意のクラスのオブジェクトでうまく機能することに注意してください。

Self=nil で呼び出すことができ、仮想化してパラメーターを使用できる仮想メソッドを定義するにはどうすればよいですか?

私は確かに、内部仮想メソッド呼び出しの配管についての理解を深める必要があります...

注: 私の使用例では、nil オブジェクトの呼び出しは正当です。例外を非表示にするのではなく、リンクされていないオブジェクトを報告するために使用されます。例: myEdit.Text := APerson.Manager.ToName('マネージャーが定義されていません');

適切な解決策についてのアドバイスをありがとう

upd5 で Delphi 2010 を使用する


編集: AV をトリガーするコードのより完全な例を追加する

TBaseClass = class(TObject)
private
  FMyName: string;
public
  property MyName: string read FMyName;
  function ToName(MsgIfNil:string=''):string; virtual;
end;

TDerivedClass = class(TBaseClass)
private
  FSpecialName: string;
public
  property SpecialName:string read FSpecialName;
  function ToName(MsgIfNil:string=''):string; reintroduce;
end;

TBaseClass.ToName(MsgIfNil:string):string;
begin
   if (Self=nil) then
     Result := MsgIfNil
   else
     Result := MyName;
end;

TDerivedClass.ToName(MsgIfNil:string):string;
begin
  if (Self=nil) then
    Result := MsgIfNil
  else
    Result := SpecialName;
end;

// Now a sample program

var
  aPerson: TBaseClass;
  aSpecialist: TDerivedClass;

begin

aPerson := TBaseClass.Create;
aPerson.MyName := 'a person';
aSpecialist := TDerivedClass.Create;
aSpecialist.SpecialName := 'a specialist';

aSpecialist := nil; // For example sake, never do this in my use case :)
// This works here,
// but triggers an AV if ToName is marked as override instead of reintroduce
ShowMessage('Name of the specialist: '+aSpecialist.ToName('No specialist!'));

aPerson := nil;
// This triggers an AV, TBaseClass.ToName is never called
ShowMessage('Name of the person: '+aPerson.ToName('No person!'));

end;

上記のコードはコンパイルできない可能性があります。これは、より完全な例を示すことのみを目的としています。

テイクウェイ

VMT はオブジェクト参照にリンクされており、オブジェクト クラスに関係なく、nil オブジェクトで仮想メソッドを呼び出すことはできないことがわかりました (オブジェクトは宣言された型を調べて、ToName メソッドの一致するアドレスを取得することさえしません)。 )

私は hvd のソリューションを受け入れました。これは、vs nil をチェックする必要があるメソッド (追加する基本メソッドは 1 つだけ) に非常に効果的であるためです。

すべての回答に感謝します。

4

3 に答える 3

6

で仮想メソッドを呼び出すnilことは意味がありません。virtual「クラスタイプをチェックして、呼び出すメソッドを確認する」ことを意味します。クラスタイプがないため、呼び出すメソッドはありません。

できることは、仮想メソッドを呼び出す非仮想メソッドを作成することです。

// TBase
public:
    function ToName(MsgIfNil: string = ''): string;
protected:
    function ToNameImpl: string; virtual;

// TDerived
protected:
    function ToNameImpl: string; override;

function TBase.ToName(MsgIfNil: string): string;
begin
  if (Self=nil) then
    Result := MsgIfNil
  else
    Result := ToNameImpl;
end;

function TBase.ToNameImpl: string;
begin
  Result := MyName;
end;

function TDerived.ToNameImpl: string;
begin
  Result := MyDerivedName;
end;

これによりToNameImpl、仮想メソッドであるが、が呼び出されSelfない場合にのみ呼び出されるようになりnilます。

編集:ちなみに、これはまさに非TObject.Free仮想が仮想を呼び出すために行うことTObject.Destroyです。

于 2012-07-19T09:57:17.280 に答える
4

Self=nil で呼び出すことができ、仮想化してパラメーターを使用できる仮想メソッドを定義するにはどうすればよいですか?

仮想メソッド呼び出しには VMT が必要になるため、これは Delphi では実行できません。また、nil オブジェクトには VMT がありません。

私の使用例では、nil オブジェクトの呼び出しは正当です。

ロジックを再考する必要があります。たとえば、ある種の「空」オブジェクトを作成できます。この場合、APerson.Manager は、特別な動作を持つ TBaseClass の先祖であるこの特別なオブジェクトを返します。サンプルコード:

TManager = class
//...
function GetSalary: integer; virtual;
procedure SetSalary(ASalary: integer) virtual;
end;

TEmptyManager = class(TManager)
//...
function GetSalary: integer; override;
procedure SetSalary(ASalary: integer) override;
end;
//...
function TManager.GetSalary: integer;
begin
//Some calculations here
end;

procedure TManager.SetSalary(ASalary: integer);
begin
//Some work here
end;

function TEmptyManager.GetSalary: integer;
begin
  Result := 0;
end;

procedure TEmptyManager.SetSalary(ASalary: integer) override;
begin 
  //Some sort of safety belt
  raise EException.Create('You can''t work with empty manager');
end;

var
  EManager: TEmptyManager = Nil;
//Since we won't work with empty manager, one instance will be enough
function EmptyManager: TManager;
begin
  if not Assigned(EManager) then
    EManager := TEmptyManager.Create;

  Result := EManager;
end;
//...
function TPerson.GetManager: TManager;
begin
  if SomeCondition then
    Result := FManager
  else
    Result := EmptyManager;
end;
于 2012-07-19T09:52:12.057 に答える
4

理論的には nil オブジェクトのメソッドを呼び出すことができます。しかし、この慣行は非常に望ましくなく、危険です。避けてください。ロジックを再考してください。クラスメソッドを見てみましょう。

それらは、多くの制限がある「静的」メソッドのように機能します。Self を含むプロパティや継承されたプロパティを参照するプロパティやメソッドにアクセスすることはできません。オブジェクトが単に存在しないためです。

オブジェクトは、メソッド呼び出し、プロパティ アクセスの前に有効である必要があります。

関数が nil になる可能性のあるオブジェクト インスタンスを返す場合、または特定の状況でオブジェクトが nil になる可能性がある場合は、メソッド呼び出しまたはプロパティ アクセスの前に確認する必要があります。

O := MyFactory.GetObject;
if Assigned(O) then O.MyMethod;
于 2012-07-19T10:21:09.603 に答える