我有两个类:一个基类和一个派生类 基类使用参数
定义虚方法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的方法非常有效(只需添加一个基本方法)。
感谢所有答案,
答案 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;