Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: how to make several virtual methods link to the same function?

I have abstract iterator class:

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;

and lots of descendants, say, T1BitImageIterator, T4BitImageIterator, T8BitImageIterator, T24BitRGBImageIterator. The point is to create iterator (over non-rectangular area) suitable for current image/pixel format and then process image regardless of its type.

ReadNextPixel returns 'raw pixel data': 0 or 1 in 1-bit image, 0..255 in 8-bit image or some sort of TColor in 24-bit RGB.

ReadNextAsGrayscale returns luminance of pixel in range of 0..255, which in case of 8 bit image is just the same as ReadNextPixel.

ReadNextSubpixel works the same as ReadNextPixel in grayscale/palette images, but returns R value, then (in next call) G value, then B value in 24 bit RGB image.

Right now I've got implementations like this:

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;

These functions should be as fast as possible, so extra function call seems ugly, but copy-pasting the whole functions is even worse!

What I'd like to do is something like this:

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

but there is no such syntax of course.

It can be done with interfaces, but they are much slower then virtual functions calls. Also, it's possible to define

TReadNextPixelProc = function: Cardinal of object;

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

and then initialize this variable in constructors, but this still have extra cost and makes these iterators more difficult to use (we must remember what is TReadNextPixelProc etc).

We can also speed-up function call:

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

so after executing ReadNextPixel we won't come back to ReadNextAsGrayscale, but return to place where ReadNextPixel was called..

But all these solutions don't seem right: I see no reason why couldn't 2 VMT entries have the same function pointer, so there would be only one copy of this function which is called 'by any other name' without extra cost. Is it possible to make it so?

like image 536
Yuriy Afanasenkov Avatar asked Mar 13 '23 19:03

Yuriy Afanasenkov


2 Answers

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

Not really. Interfaces are nothing but virtual function calls, so the performance in calling your functions should be the same as the code you have shown.

Maybe you are thinking that the reference counting is the bottleneck instead. You can disable reference counting, either in the interface implementation classes themselves, or at the call sites where interface pointers are being passed around.

Interfaces are the solution to your problem. They provide an explicit syntax to do exactly what you are asking for, using a Method Resolution Clause. For example:

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;

All of the interface methods funnel through the single DoReadNext() method, which descendants can override as needed. If a derived class wants to implement the interface methods differently, it can simply implement the desired method(s) directly, ignoring the resolution clauses in the base class. For example:

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;

You cannot do the same with classes by themselves, there is no syntax for that. You would have to hack their VMTs directly at runtime, such as at program startup.

like image 181
Remy Lebeau Avatar answered Apr 26 '23 16:04

Remy Lebeau


Declare a private inline method that does the work, and call it from each of the three virtual methods. The inline engine sometimes does not produce the most efficient code so do check how it performs by inspecting the generated code.

This approach duplicates the code in the executable, but avoids doing so in the source code. A clever compiler/linker would be able to remove this duplication but the Delphi tooling has never had such capability. It would be perfectly possible to hack the VMT but I personally wouldn't go that route. I don't think you'll achieve a discernible gain.

like image 38
David Heffernan Avatar answered Apr 26 '23 16:04

David Heffernan