接口方法总是虚拟的吗?

时间:2016-03-21 17:35:42

标签: class delphi interface virtual

编译以下代码时出错:

TOmniParallelSimplePooledLoop = class(TOmniParallelSimpleLoop)
  procedure Execute(loopBody: TOmniIteratorSimpleSimpleDelegate); overload; override;
  

[dcc64 Error] OtlParallel.pas(846):E2170无法覆盖非虚方法

如果我将祖先方法设为虚拟,那么错误就会消失。

然而,祖先方法在:

中声明
IOmniParallelSimpleLoop
  ...
  procedure Execute(loopBody: TOmniIteratorSimpleSimpleDelegate); overload;

TOmniParallelSimpleLoop中基本方法从非虚拟到虚拟的重新声明会改变基类型,还是已经虚拟的方法开始(由于它是接口方法的实现)?

换句话说:当接口方法从非虚拟变为虚拟时,编译器会输出不同的代码吗?

基本MSVC重新创建错误

program Project70;
{$APPTYPE CONSOLE}
uses
  System.SysUtils;

type
  I1 = interface
    procedure DoSomething;
  end;

  T1 = class(TInterfacedObject, I1)
    procedure DoSomething;
  end;

  T2 = class(T1)
    procedure DoSomething; override;
  end;

procedure T1.DoSomething;
begin
  WriteLn('parent');
end;

procedure T2.DoSomething;
begin
  Writeln('Child');
end;

begin
end.

2 个答案:

答案 0 :(得分:8)

  

接口方法总是虚拟的吗?

接口方法既不是虚拟的也不是非虚拟的。该概念不适用于接口方法。

另一方面,类的方法可以是虚拟的或非虚拟的。区别决定了方法调用的绑定方式。虚拟方法在运行时绑定,考虑到对象的运行时类型。并且在编译时绑定非虚方法,使用对象引用的编译时类型。

编译器错误只是告诉您override仅对虚拟方法有意义。您的代码正在尝试对非虚拟方法使用override。考虑一下这个程序,它根本不包含任何接口:

type
  T1 = class
    procedure DoSomething;
  end;

  T2 = class(T1)
    procedure DoSomething; override;
  end;

procedure T1.DoSomething;
begin
end;

procedure T2.DoSomething;
begin
end;

begin
end.

此程序无法编译与程序完全相同的错误。该错误与接口无关。在DoSomething中引入T1时将其声明为虚拟将解决错误。

  

TOmniParallelSimpleLoop中基本方法从非虚拟到虚拟的重新声明是否会更改基类型?

是的,它会。它将该方法从非虚拟更改为虚拟。这意味着如上所述,方法调度以不同方式执行。这意味着类型的VMT会更改以适应新的虚拟方法。

  

或者该方法是否已经虚拟开始(由于它是接口方法的实现)?

使用方法实现接口的一部分这一事实不会改变编译器处理它的方式。无论是否实现接口方法,都以相同的方式实现非虚方法。同样适用于虚拟方法。为实现接口而生成的VMT是一个独特的问题。

详细说明,每种方法都有一个地址。在调用非虚方法时,编译器确切地知道要调用哪个方法。因此它可以发出代码直接调用该已知方法。对于虚方法,编译器不知道将调用哪个方法。这由运行时类型决定。因此编译器发出代码来读取对象VMT中的已知条目,然后调用该方法。

现在,我相信您也知道,接口也是使用VMT实现的。但这并不意味着实现方法会自动升级为虚拟。界面只是一个VMT。当被认为是类的方法时,接口VMT引用的方法可以是虚拟的或非虚拟的。

type
  ISomeInterface = interface
    procedure Foo;
  end;

  TSomeObject = class(TInterfacedObject, ISomeInterface)
    procedure Foo;
  end;

....

var
  Intf: ISomeInterface;
  Obj: TSomeObject;
....
Intf := TSomeObject.Create;
Obj := Intf as TSomeObject;

// non-virtual method, direct dispatch at compile time
Obj.SomeMethod; 

// interface method, dispatched via interface VMT
Intf.SomeMethod;

因此,可以通过VMT调用方法的事实并不意味着必须以这种方式调用它。

答案 1 :(得分:3)

我认为这个问题的答案包含在Danny Thorpe" Delphi Component Design",ISBN 0-201-46136-6的一句中,我在@ DavidH&的评论中提到过回答:

" VMT正是一个函数指针数组",在"从DLL中导入对象 - 艰难的方式",p.89。

以下部分"从DLL导入对象 - 智能方式"解释了如何使用一个抽象接口成员的函数指针数组来调用实现接口的DLL,以及如何(天才的行程,而不仅仅是imo)提供了Delphi的COM支持的基础(一次)它通过D2的变种得到了过去的支持。