Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Help me understand TClass 1

Status
Not open for further replies.

Griffyn

Programmer
Jul 11, 2002
1,077
AU
Hi all,

I thought I knew how this worked, but recently encountered something that threw me. Could you please help me understand what's going on?

The sample code below creates a fresh copy of an existing object.

Code:
[b]type[/b]
  TBaseObject = [b]class[/b]
  [b]private[/b]
    FFlag : Boolean;
  [b]public[/b]
    [b]constructor[/b] Create; [b]virtual[/b];
  [b]end[/b];

  TMyObject = [b]class[/b](TBaseObject)
  [b]public[/b]
    [b]constructor[/b] Create; [b]override[/b];
  [b]end[/b];

  TMyClass = [b]class[/b] [b]of[/b] TBaseObject;

[b]constructor[/b] TBaseObject.Create;
[b]begin[/b]
  [b]inherited[/b];
[b]end[/b];

[b]constructor[/b] TMyObject.Create;
[b]begin[/b]
  [b]inherited[/b];
  FFlag := True;
[b]end[/b];

[b]procedure[/b] TForm1.Button1Click(Sender: TObject);
[b]var[/b]
  a : TBaseObject;
  b : TObject;
[b]begin[/b]
  a := TMyObject.Create;
  b := a.ClassType.Create;  [navy][i]// a.ClassType = TMyObject[/i][/navy]
  [b]try[/b]
    CheckBox1.Checked := a.FFlag;
    CheckBox2.Checked := TBaseObject(b).FFlag;
  [b]finally[/b]
    b.Free;
    a.Free;
  [b]end[/b];
[b]end[/b];

In this example, only the CheckBox1.Checked = True;

I can fix this by changing the line that creates the b object to
Code:
b := TMyClass(a.ClassType).Create;

With this change, both checkboxes are checked. But I don't understand why.

Thanks for your help.

 
I compiled your code as a console app, and put writelns before the assigns and in the constructors.

The original code you posted.
Code:
a assigned
MyObject created.
Base object created.
b assigned

Your change.
Code:
a assigned
MyObject created.
Base object created.
b assigned
MyObject created.
Base object created.

The second one is showing up as checked because you are creating another TMyObject class in the second case, as opposed to the TBaseObject in the first case.

But the short answer is that a.ClassType is the descendant class (TMyObject) of the class you use in TMyClass (TBaseObject). As it is the base class of TMyObject, there is no problem behind the *implementation* of the code (though the idea is off, obviously), and TMyObject is getting created, and in process setting the flag. Now if you present different classes (say I make TMyClass a class of TEdit), then I'll get an access violation.


It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Because you are using class references to construct b, the compiler looks for the most common denominator when deciding which constructor to use. In your first case, you declared b to be a TObject, and that is the base of all classes declared with a "class" declaration, so that is the constructor used for b. In the second, you forced the compiler to use TBaseObject's constructor with the typecast.
 
I wish they would let you pull posts in here. My understanding was incorrect too. I am still trying to work it out. I do know that the documentation says to use the Keywords "is" and "as" instead of ClassType for working with Class references. It also says not to use ClassType in application code, I guess because of inconsistencies on how it is implemented.
 
After a little more research, I have found this out: You need to overload the ClassType function in your TBaseClass to return a TMyClass pointer, like so:

Code:
interface
type
  TMyClass = class of TBaseObject;

  TBaseObject = class
  private
  public
    FFlag : Boolean;
    constructor Create;virtual;
    function ClassType:TMyClass;overload;inline;
  end;

  TMyObject = class(TBaseObject)
  public
    constructor Create;override;
  end;

implementation

constructor TBaseObject.Create;
begin
  inherited;
end;

constructor TMyObject.Create;
begin
  inherited;
  FFlag := True;
end;

function TBaseObject.ClassType:TMyClass;
begin
  Pointer(Result) := PPointer(Self)^;
end;
end.

Your classes are all descended from TObject, which has implements the ClassType function such that it returns a TObject for a class type. If not overloaded, the class type will return the closest implementation of ClassType, or TObject in this case. Once you implement the overloaded ClassType function, because TBaseObject's constructor is declared virtual and TMyClass's is an override of TBaseClass's, TMyClass's constructor will be the one that will be called in your program, even though the ClassType returned is TBaseObject. TObject's constructor is not virtual, which is why your first version did not work as you expected.
 
Hi guys,

Thanks for your time looking at this. You've both arrived at the same conclusion - even though a.ClassType = TMyObject, Delphi is creating a TObject instead - confirmed again because if I declare b as a TBaseObject instead of TObject, it won't compile because the assignment is trying to assign a TObject to b.
 
a.ClassType does NOT = TMyObject, unless you overload the ClassType function in TBaseObject to return a TMyClass; that was the whole point of my most recent post. In your original Code, a.ClassType = TObject, not TMyObject, because you have not overloaded it. I suppose this is why the documentation strongly discourages the use of ClassType, as it is not guaranteed to be implemented and so will result in inconsistent behavior.
 
If I set up a breakpoint on the line where b is assigned, and then add a watch for a.ClassType, it evaluates to TMyObject. Thus my confusion.
 
The TObject ancestor method returns a dereferenced self pointer as a TObject class. Since the Class reference's address is the same as the actual object (since any object type declared with a "class" declaration are descended from TObject, and therfore IS a TObject), my guess is the debugger probably interprets that as being the same class as the actual object. I do know that even if you overload the ClassType function to return a TMyBaseObject class reference, the debugger still shows a.ClassType as TMyObject. But the Compiler will only "see" the TObject being returned by TObject.ClassType, so that is the Constructor that gets called for b, since TObject's constructor is not declared as virtual.

I hope all this has helped you to a better understanding of what is going on.
 
That makes more sense. You've been a great help. Thank you.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top