Page 1 of 2 12 LastLast
Results 1 to 10 of 13

Thread: Constructor casting

  1. #1

    Constructor casting

    Hello.

    Sorry for the misleading topic title, but I don't really know how to describe it. :? First of all, consider this code:
    [pascal]
    program Project1;

    {$APPTYPE CONSOLE}

    uses
    FastMM4,
    Classes,
    SysUtils;

    type
    { .: TClass1 :. }
    TClass1 = class(TObject)
    private
    { Private declarations }
    FVal: Single;
    FList: TList;
    public
    { Public declarations }
    constructor Create();
    destructor Destroy(); override;

    function AddIt(): TClass1;
    property X: Single read FVal write FVal;
    end;

    { .: TClass2 :. }
    TClass2 = class(TClass1)
    public
    { Public declarations }
    constructor Create(const X: String);

    procedure WriteVal();
    end;

    { TClass1 }

    function TClass1.AddIt: TClass1;
    begin
    Result := TClass1.Create();
    FList.Add(Result)
    end;

    constructor TClass1.Create;
    begin
    inherited Create();

    FList := TList.Create();
    end;

    destructor TClass1.Destroy;
    var
    I: Integer;
    begin
    for I := FList.Count -1 downto 0 do
    TClass1(FList.Items[I]).Free();
    FList.Clear();
    FList.Free();

    inherited Destroy();
    end;

    { TClass2 }

    constructor TClass2.Create(const X: String);
    begin
    inherited Create();

    writeln('From class2: ', X);
    end;

    procedure TClass2.WriteVal;
    begin
    writeln(X:0:2);
    end;

    var
    C: TClass1;
    begin
    C := TClass1.Create();
    try
    C.X := 3.14;
    with TClass2(C.AddIt()) do
    begin
    X := 5.25;
    WriteVal();
    end;
    finally
    FreeAndNil(C);
    end;

    readln;
    end.
    [/pascal]

    Now I'd like to know how to call the TClass2 constructor using that casting:
    [pascal]
    with TClass2(C.AddIt()) do
    [/pascal]

    Is it possible? :?

    Thank you in advance and I hope it's clear for all.

  2. #2

    Constructor casting

    As far as i can tell it's possible.

    Some time ago i've seen this:

    Code:
      with TMyObjectThing.Create(MyParam) do
      begin
        //do things with TmyObjectThing
    
      end;
    Did you test the code?
    Coders rule nr 1: Face ur bugz.. dont cage them with code, kill'em with ur cursor.

  3. #3

    Constructor casting

    This one works,
    [pascal]
    with TClass2(C.AddIt()).Create('dox') do
    [/pascal]
    But only partially, as FastMM informs about memory leaks from TClass1. :?

  4. #4

    Constructor casting

    Indeed. C.AddIt() will not be freed in code and results memory leak that i don't know how Delphi handles.

    Edit:[pascal] with TClass2(C.AddIt()) do
    begin
    X := 5.25;
    WriteVal();
    Free; // Add this and no memory leak
    end;[/pascal]

    However, because Class2 may have properties that Class1 don't, there may be problems with compatibility. C.AddIt() will only reserve memory for variables in TClass1, anything that goes to TClass2 goes out of its memory.. sort of access violation at least in theory

  5. #5

    Constructor casting

    Hmm, what are you trying to achieve?

    Testing in FPC in Delphi mode (just removed FastMM and compiled with "fpc -Mdelphi -gh a.pas", -gh adds memory leak checking) the code works. It should also work with Delphi, at least non-.net versions, as basic class implementation is the same there (as in all OOP languages). However, it works only by accident, as far as I see.

    Reason: you create in TClass1.AddIt an instance of class TClass1. But later you want to cast it to class TClass2, which is simpy an invalid cast... The fact that it works, without introducing any memory leaks or access violations, is only an coincidence, a result of how compilers implement classes and a result of the fact that TClass2 does not introduce any new variables and does not introduce any new virtual methods.

    But you're doing something inherently wrong, without any guarantee that it will always work... Changing unchecked cast "TClass2(C.AddIt())" to a checked cast "C.AddIt() as TClass2" reports "Invalid type cast", showing that the cast is wrong.

    If you really need to cast C.AddIt result to a TClass2, you should just create TClass2 inside TClass1.AddIt. Like

    [pascal]
    ........ { replacing with dots unchanged code }

    type
    { Forward reference only, TClass2 wil be declared later. }
    TClass2 = class;

    TClass1 = class(TObject)
    .......
    function AddIt(): TClass2;
    .......
    end;

    TClass2 = class(TClass1)
    ......

    .........

    function TClass1.AddIt: TClass2;
    begin
    Result := TClass2.Create('blah');
    FList.Add(Result)
    end;

    ............

    var
    C: TClass1;
    begin
    C := TClass1.Create();
    try
    C.X := 3.14;
    with C.AddIt() do
    begin
    X := 5.25;
    WriteVal();
    end;
    finally
    FreeAndNil(C);
    end;

    readln;
    end.
    [/pascal]

    Now the cast is even not needed, and all is correct.

  6. #6

    Constructor casting

    If You want something more flexible, You can use class objects to create any object derived from TClass1. But it requires that all constructors have same list of parameters. Here is a sample code:
    [pascal]

    type
    TClass1 = class;
    TClass1Object = class of TClass1; //a class object (metaclass)

    TClass1 = class(TObject)
    ...
    constructor Create;
    function AddIt(classType : TClass1Object): TClass1;
    ...
    end;

    TClass2 = class(TClass1)
    ...
    constructor Create; //must have same signature as TClass1.Create
    end;

    ...

    function TClass1.AddIt(classType : TClass1Object): TClass1;
    begin
    Result := classType.Create;
    FList.Add(Result)
    end;

    ...

    var
    C: TClass1;
    begin
    C := TClass1.Create();
    try
    C.X := 3.14;
    with C.AddIt(TClass2) as TClass2 do
    begin
    X := 5.25;
    WriteVal();
    end;
    finally
    FreeAndNil(C);
    end;

    readln;
    end.
    [/pascal]

  7. #7

    Constructor casting

    I'm aiming for something very similar like grudzio suggested, but I'd like to have different parameters in constructors. Is it possible at all? :roll:

  8. #8

    Constructor casting

    Quote Originally Posted by grudzio
    If You want something more flexible, You can use class objects to create any object derived from TClass1. But it requires that all constructors have same list of parameters.
    Remember that constructor must be virtual in TClass1, and overridden in TClass2 and other descendants then. Otherwise classType.Create always calls TClass1 constructor. As a side effect, compiler will also check then that Create "signatures" are matching in both classes (you can override only existing proc..).

  9. #9

    Constructor casting

    First thing that comes to my mind is to leave constructors as they are and add Init procedure with dofferent parameters. So the code would look like this.
    [pascal]
    type
    TClass1 = class;
    TClass1Object = class of TClass1; //a class object (metaclass)

    TClass1 = class(TObject)
    ...
    constructor Create; virtual;
    procedure Init; virtual;
    function AddIt(classType : TClass1Object): TClass1;
    ...
    end;

    TClass2 = class(TClass1)
    ...
    constructor Create; override; //must have same signature as TClass1.Create
    procedure Init(const s : string); reintroduce;
    end;

    ...

    function TClass1.AddIt(classType : TClass1Object): TClass1;
    begin
    Result := classType.Create;
    FList.Add(Result)
    end;

    procedure TClass1.Init;
    begin
    writeln('Simple init');
    end;

    ...

    procedure TClass2.Init(const s : string);
    begin
    writeln('Init '+s);
    end;

    var
    C,C1: TClass1;
    begin
    C := TClass1.Create();
    try
    C.X := 3.14;
    C1 := C.AddIt(TClass2);
    if C1 is TClass2 then
    (C1 as TClass2).Init('C1')
    else
    C1.Init;
    C1.X := 5.25;
    WriteVal();
    finally
    FreeAndNil(C);
    end;

    readln;
    end.
    [/pascal]
    But there is a problem with the if statement. If You add another class (TClass3) with another version of Init then You need to update the if statement. That is why constructors (and init functions) should have same signature. The only way to fix it that I can think of, is to use some flexible data format which can hold different data types. This could be XML, JSON or your own custom data format. So the code would be like this.
    [pascal]
    type
    TClass1 = class;
    TClass1Object = class of TClass1; //a class object (metaclass)

    TClass1 = class(TObject)
    ...
    constructor Create; virtual;
    procedure Init(data : TXMLNode); virtual;
    function AddIt(classType : TClass1Object): TClass1;
    ...
    end;

    TClass2 = class(TClass1)
    ...
    constructor Create; override;//must have same signature as TClass1.Create
    procedure Init(data : TXMLNode); override;
    end;

    ...

    function TClass1.AddIt(classType : TClass1Object): TClass1;
    begin
    Result := classType.Create;
    FList.Add(Result)
    end;

    procedure TClass1.Init(data : TXMLNode);
    begin
    //do something with data
    end;
    ...

    procedure TClass2.Init(data : TXMLNode);
    begin
    //do something else with data
    end;

    var
    C: TClass1;
    data : TXMLNode;
    begin
    loadData(data); //get data
    C := TClass1.Create();
    C.Init(nil);
    try
    C.X := 3.14;
    with C.AddIt(TClass2) do //type casting is not needed anymore
    begin
    X := 5.25;
    Init(data);
    WriteVal();
    end;
    finally
    FreeAndNil(C);
    end;

    readln;
    end.
    [/pascal]
    Of cousrse, the Init procedure can be integrated into the AddIt function.
    [EDIT] I've corrected the code as pointed by Michalis.

  10. #10

    Constructor casting

    Quote Originally Posted by Brainer
    I'd like to have different parameters in constructors. Is it possible at all? :roll:
    Overridden constructors must have the same parameters, otherwise compiler doesn't know how to call them. (And you don't know that either --- you do you want to call classType.Create, if you don't know whether it should be classType.Create(123) or classType.Create('aaa') ?).

    Various solutions are possible here. E.g. you can make a couple of overloaded virtual constructors. Or you can make one constructor that takes something like "a: array of const", this will allow you to pass variable-length array with any types, like for Format. Or you can use variants (many Pascal programmers dislike them, but sometimes...).

Page 1 of 2 12 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •