1

我有两个类:一个基类和一个派生类基类定义了一个带有参数的虚拟方法:

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 的方法非常有效(只需添加一种基本方法)。

谢谢大家的回答,

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;

这确保了虚拟方法仅在is notToNameImpl时才被调用。Selfnil

编辑:顺便说一句,这正是 nonvirtualTObject.Free所做的调用 virtual TObject.Destroy

于 2012-07-19T09:57:17.280 回答
4

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

这不能在 delphi 中完成,因为您需要一个 VMT 来进行虚拟方法调用。并且 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 回答