Delphi:如何使多个虚拟方法链接到同一个函数?

标签 delphi iterator polymorphism

我有抽象迭代器类:

TImageIterator = class (TObject)
  protected
    fCurLine, fCurPos: Integer;
  public
    //also basic constructors here, etc.
    function ReadNextPixel: Cardinal; virtual; abstract;
    function ReadNextAsGrayscale: Cardinal; virtual; abstract;
    function ReadNextSubpixel: Cardinal; virtual; abstract;
end;

以及许多后代,例如,T1BitImageIteratorT4BitImageIteratorT8BitImageIteratorT24BitRGBImageIterator。重点是创建适合当前图像/像素格式的迭代器(在非矩形区域上),然后处理图像,无论其类型如何。

ReadNextPixel 返回“原始像素数据”:1 位图像中的 0 或 1,8 位图像中的 0..255 或 24 位 RGB 中的某种 TColor。

ReadNextAsGrayscale 返回 0..255 范围内的像素亮度,在 8 位图像的情况下与 ReadNextPixel 相同。

ReadNextSubpixel 与灰度/调色板图像中的 ReadNextPixel 工作方式相同,但返回 R 值,然后(在下一次调用中)G 值,然后返回 24 位 RGB 中的 B 值图片。

现在我有这样的实现:

function T8BitImageIterator.ReadNextPixel: Cardinal;
begin
  Result:=fByteLine[fCurPos];
  inch(fCurPos);
  if fCurPos=fRight then begin
    //going to next scanline, checking for end of iterated area etc
  end;
end;

//another 'unique' functions

//code we'd be happy to get rid of
function T8BitImageIterator.ReadNextAsGrayscale: Cardinal;
begin
  Result:=ReadNextPixel;
end;

function T1BitImageIterator.ReadNextSubpixel: Cardinal;
begin
  Result:=ReadNextPixel;
end;

function T4BitImageIterator.ReadNextSubpixel: Cardinal;
begin
  Result:=ReadNextPixel;
end;

function T8BitImageIterator.ReadNextSubpixel: Cardinal;
begin
  Result:=ReadNextPixel;
end;

这些函数应该尽可能快,所以额外的函数调用看起来很难看,但复制粘贴整个函数更糟糕!

我想做的是这样的:

  T8BitImageIterator = class (TImageIterator)
    public
      function ReadNextPixel: Cardinal; override;
      function ReadNextAsGrayscale=ReadNextPixel; override;
      ...
  end;

但是当然没有这样的语法。

可以通过接口(interface)来完成,但它们比虚拟函数调用慢得多。此外,还可以定义

TReadNextPixelProc = function: Cardinal of object;

TImageIterator = class(TObject)
  ...
  public
    ReadNextAsGrayscale: TReadNextPixelProc;
    ...
 end;

然后在构造函数中初始化这个变量,但这仍然有额外的成本,并使这些迭代器更难以使用(我们必须记住什么是 TReadNextPixelProc 等)。

我们还可以加速函数调用:

function T8BitImageIterator.ReadNextAsGrayscale: Cardinal;
asm
  jmp ReadNextPixel;
end;

所以执行完ReadNextPixel之后我们不会回到ReadNextAsGrayscale,而是返回到调用ReadNextPixel的地方..

但是所有这些解决方案似乎都不正确:我看不出为什么两个 VMT 条目不能具有相同的函数指针,因此只有一个该函数的副本,该副本被“以任何其他名称”调用,而无需额外开销。可以做到吗?

最佳答案

It can be done with interfaces, but they are much slower then virtual functions calls

不是真的。接口(interface)只不过是虚拟函数调用,因此调用函数的性能应该与您显示的代码相同。

也许您认为引用计数才是瓶颈。您可以在接口(interface)实现类本身或传递接口(interface)指针的调用站点处禁用引用计数。

接口(interface)是您问题的解决方案。它们提供了明确的语法来准确地执行您所要求的操作,使用 Method Resolution Clause 。例如:

type
  IImageIterator = interface
    function ReadNextPixel: Cardinal;
    function ReadNextAsGrayscale: Cardinal;
    function ReadNextSubpixel: Cardinal;
  end;

  TImageIteratorBase = class(TInterfacedObject, IImageIterator)
  protected
    fCurLine, fCurPos: Integer;
  public
    //basic constructors here, etc.
    function DoReadNext: Cardinal; virtual; abstract;
    function IImageIterator.ReadNextPixel = DoReadNext;
    function IImageIterator.ReadNextAsGrayscale = DoReadNext;
    function IImageIterator.ReadNextSubpixel = DoReadNext;
  end;

  T1BitImageIterator = class(TImageIteratorBase, IImageIterator)
  public
    function DoReadNext: Cardinal; override;
    // implement IImageIterator methods if needed...
  end;

  T4BitImageIterator = class(TImageIteratorBase, IImageIterator)
  public
    function DoReadNext: Cardinal; override;
    // implement IImageIterator methods if needed...
  end;

  T8BitImageIterator = class(TImageIteratorBase, IImageIterator)
  public
    function DoReadNext: Cardinal; override;
    // implement IImageIterator methods if needed...
  end;

  T24BitRGBImageIterator = class(TImageIteratorBase, IImageIterator)
  public
    function DoReadNext: Cardinal; override;
    // implement IImageIterator methods if needed...
  end;

...

function T1BitImageIterator.DoReadNext: Cardinal;
begin
  //...
end;

function T4BitImageIterator.DoReadNext: Cardinal;
begin
  //...
end;

function T8BitImageIterator.DoReadNext: Cardinal;
begin
  Result:=fByteLine[fCurPos];
  inch(fCurPos);
  if fCurPos=fRight then begin
    //going to next scanline, checking for end of iterated area etc
  end;
end;

function T24BitImageIterator.DoReadNext: Cardinal;
begin
  //...
end;

所有接口(interface)方法都通过单个 DoReadNext() 方法,后代可以根据需要覆盖。如果派生类想要以不同的方式实现接口(interface)方法,它可以直接实现所需的方法,忽略基类中的解析子句。例如:

type
  ...
  T24BitRGBImageIterator = class(TImageIteratorBase, IImageIterator)
  public
    function DoReadNext: Cardinal; override;
    // implement IImageIterator methods if needed...
    function ReadNextAsGrayscale: Cardinal;
  end;

function T24BitRGBImageIterator.DoReadNext: Cardinal;
begin
  //...
end;

function T24BitRGBImageIterator.ReadNextAsGrayscale: Cardinal;
begin
  //...
end;

您不能对类本身执行相同的操作,没有相应的语法。您必须在运行时直接破解他们的 VMT,例如在程序启动时。

关于Delphi:如何使多个虚拟方法链接到同一个函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34446169/

相关文章:

c++ - 在 C++ STL 映射中遍历时删除会导致运行时错误

java - 为什么迭代器仍然存在于java中

c# - 向上转换而不保留对派生类型的引用

c++ - C++ 中的动态实例化

c++ - 是否可以在 C++ 中使用转换迭代器?

java - Java 国际象棋游戏中的多态性

sql - SQL 语句所有可能的第一个单词是什么?

excel - 运行delphi客户端自动化程序后excel.exe保持加载状态的原因是什么?

windows - Delphi:创建和读取文本文件导致 I/O 32 错误。应该使用 sleep 来防止这种情况吗?

delphi - Delphi 6可以安装在Windows 10上吗