Friday, January 09, 2015

Implementing Record Assignment Operator [1]

Following the yesterday’s hunch, I did some research and I came up with a way to detect when a record is copied (simple, supported, working everywhere) and even to access both records (source and target) during that operation (unsafe, unsupported and currently working only for Win32).

Now I can write such code:

program RecordAssignment;

{$APPTYPE CONSOLE}

{$R *.res}

uses
FastMM4,
System.SysUtils,
GpRecAssignOverload in 'GpRecAssignOverload.pas';

type
TRec = record
private
FData : integer;
FAssignOp: IInterface; //should be last field in the record!
public
constructor Create(data: integer);
end;

{ TRec }

constructor TRec.Create(data: integer);
begin
FData := data;
Writeln('Record [', FData, '] created');

FAssignOp := TAssignRec<TRec>.Create(
procedure(var rLeft, rRight: TRec)
begin
Writeln('Record assignment');
rLeft.FData := rLeft.FData + 1;
end
);
end;

procedure Test;
var
rec1, rec2: TRec;
begin
rec1 := TRec.Create(42);
rec2 := rec1;
Writeln('rec1.FData = ', rec1.FData);
Writeln('rec2.FData = ', rec2.FData);
end;

begin
try
Test;
Write('>'); Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end
.

And the output is:


image


Just to make something clear – yes, technically I can write this but I’m pretty sure that I don’t want to. Tomorrow I’ll show the implementation for TAssignRec and you’ll see why.

11 comments:

  1. Could this be solved with operator overloading?
    Like this example: https://plus.google.com/+Asbj%C3%B8rnHeid/posts/KnR9pUPTy7P

    Or will Implicit/Explicit only be triggered when there's a typecast involved?

    ReplyDelete
    Replies
    1. Explicit is only triggered when typecast is involved.

      Implicit is a good idea, but it doesn't work.

      class operator Implicit(const a: TRec): TRec;

      [DCC Error] CatchAssignment.dpr(39): E2521 Operator 'Implicit' must take one 'TRec' type in parameter or result type

      Delete
    2. Ah. Just figured that the operator overloading is intended for different datatypes and not similar datatypes. I had the impression that it was possible to override default behaviour as well...

      Delete
  2. @Jorn: no, assignment can be overloaded in C++ but not in Delphi.

    @Gabr: instead of using a dummy interface to intercept an assignment why not to move a record data into an instance implementing interface? this is essentially a basic idea of my TForge project. You get solid automatic memory management with clear and safe "user" code, and very interesting and highly effective "engine" code.

    ReplyDelete
    Replies
    1. Serge, do you have any blog post on that? I'm collecting various alternative (and better) solutions for the next post on that topic.

      Delete
    2. I believe this one is the most appropriate - http://sergworks.wordpress.com/2013/12/17/goodbye-tobject/

      Delete
    3. There's not really much on the topic of records here.

      Delete
  3. If you go through pointers, you can have something like this.
    (You need to add @ in front of the records you want to run through the custom assignment)



    type
    TRec = record
    private
    FData : integer;
    public
    constructor Create(iData: integer);
    class operator Implicit(aSource: Pointer): TRec;
    end;

    //-----

    class operator TRec.Implicit(aSource: Pointer): TRec;
    type
    TTempRec = type TRec;
    begin
    Result.FData := TTempRec(aSource^).FData + 1;
    end;

    //-----

    procedure Test;
    var
    rec1, rec2, rec3: TRec;
    begin
    rec1 := TRec.Create(42);
    rec2 := rec1; //Plain assignment
    rec3 := @rec1; //Custom assignment


    Writeln('rec1.FData = ', rec1.FData);
    Writeln('rec2.FData = ', rec2.FData);
    Writeln('rec3.FData = ', rec3.FData);
    end;

    //-----


    Gives you:

    rec1.FData = 42
    rec2.FData = 42
    rec3.FData = 43

    ReplyDelete
    Replies
    1. Obviously a source for lots for debugging :-P

      Delete
  4. Dirty hack, but works :D

    program project1;
    type
    Tfoo=record
    a: string;
    end;
    Tbar = record
    a: string;
    end;
    // I use two equivalent types

    // and two assigment op's: bar->foo and foo->bar:
    operator := (foo: TFoo): TBar;
    begin
    result.a:=foo.a;
    end;
    operator := (bar: TBar): TFoo;
    begin
    result.a := bar.a;
    end;
    var
    a,b:Tfoo;
    begin
    a.a:='a';
    b.a:='b';
    a := b;
    writeln(a.a);
    readln;
    end.

    ReplyDelete
    Replies
    1. 'operator :=' is not a syntax supported by Delphi.

      Delete