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

时间:2012-07-19 09:43:52

标签: delphi access-violation null

我有两个类:一个基类和一个派生类 基类使用参数

定义虚方法
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)如果我没有在派生类中重新引入该方法,但使用常规override关键字,则对此方法的任何调用都会触发访问冲突

2)当我从一个nil的对象调用该方法,并且objet的假定类是TBaseObject时,它崩溃(AV)而不是调用基本虚方法

如果方法中没有定义参数,则调用正确的方法,不带任何AV。即使派生类中的方法被覆盖,它也能正常工作。

请注意,上述解决方案适用于从TBaseClass派生的任何类的对象

如何定义可以使用Self = nil调用的虚拟方法,可以是虚拟的还是使用参数?

我当然必须加强对内部虚拟方法调用管道工程的理解......

注意:在我的用例中,调用nil对象是合法的。它不用于隐藏异常,而是用于报告非链接对象。 示例:myEdit.Text:= APerson.Manager.ToName('没有经理定义');

感谢您提供有关正确解决方案的任何建议

将Delphi 2010与upd5一起使用


编辑:添加一个更完整的触发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;

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

Takeway

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

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

感谢所有答案,

3 个答案:

答案 0 :(得分: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;

这可确保仅在ToNameImpl不是Self时才调用nil虚拟方法。

编辑:顺便说一句,这正是非虚拟TObject.Free调用虚拟TObject.Destroy的行为。

答案 1 :(得分:4)

  

如何定义可以使用Self = nil调用的虚拟方法,可以是虚拟的还是使用参数?

这不能在delphi中完成,因为你需要一个VMT来进行虚拟方法调用。 Nil-objects没有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;

答案 2 :(得分:4)

理论上你可以调用一个nil对象的方法。但这种做法非常不必要且危险。躲开它。重新思考你的逻辑。看看课程方法。

它们更像是一种具有很多限制的“静态”方法。您不能访问任何属性或方法引用包括Self在内的属性,也不能继承;因为对象根本不存在。

在任何方法调用之前,对象必须有效,属性访问。

如果你的函数返回一个可能为nil的对象实例,或者在某些情况下你的对象可能是nil,你需要在任何方法调用或属性访问之前检查它:

O := MyFactory.GetObject;
if Assigned(O) then O.MyMethod;