使用参数检查虚拟方法中的Self = nil

use*_*561 0 delphi null access-violation

我有两个类:一个基类和一个派生类基类使用参数定义一个虚方法:

function ToName(MsgIfNil:string=''); virtual;
Run Code Online (Sandbox Code Playgroud)

派生类重新定义了该方法:

function ToName(MsgIfNil:string=''); reintroduce;
Run Code Online (Sandbox Code Playgroud)

这两种方法的实现类似于以下代码:

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

问题是:

1)如果我没有在派生类中重新引入该方法,但使用常规override关键字,则对此方法的任何调用都会触发访问冲突

2)当我从一个nil的对象调用该方法,并且objet的假定类是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;
Run Code Online (Sandbox Code Playgroud)

上面的代码可能无法编译,这只是一个更完整的例子

Takeway

我现在明白VMT链接到对象引用,并且无论对象类如何,都不可能在nil对象上调用虚方法(该对象甚至不会查看其声明的类型以获取ToName方法的匹配地址)

我接受了hvd的解决方案,因为它对于必须检查vs nil的方法非常有效(只需添加一个基本方法).

谢谢你的所有答案,

小智 5

调用虚方法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;
Run Code Online (Sandbox Code Playgroud)

这确保ToNameImpl了虚拟方法仅在Self不被调用时被调用nil.

编辑:顺便说一下,这正是非TObject.Free虚拟用来调用虚拟的TObject.Destroy.