views:

153

answers:

4

If I write

type
  MyClass = class of TMyClass;
...
Obj := MyClass.Create;

the correct constructor (the one in TMyClass) is called.

If I write

var
  ClassVar : TClass;
...
ClassVar := TMyClass;
Obj := ClassVar.Create;

only the TObject constructor is called.

Why? What's the difference between the two versions? Can I force the TMyClass constructor call in the second scenario?

+9  A: 

TClass is declared as "Class of TObject" in system.pas. What constructor gets called is decided at compile-time, and all the compiler knows is what base class you're using. It doesn't know what the value of the variable will be when it runs, so it has to default to the base class. If you're using a TClass, then your base class is TObject.

If you're using a class variable, I assume you have some sort of heirarchy and you're trying to implement a factory. If you want to make sure the right constructor gets called based on the value of a class variable at runtime, not something contained in your code at compile time, you need a virtual constructor.

type
  TMyBaseObject = class(TObject)
  public
    constructor Create; virtual;
  end;

  TMyClass = class of TMyBaseObject;

Use a TMyClass instead of a TClass as your class variable, and now the compiler will generate a call to TMyBaseObject.Create, which is virtual. Make sure all your derived classes override the base constructor, and you'll end up calling the right one at runtime.

Mason Wheeler
+1 thanks! Is there anything bad about the constructor being virtual? Specifically is there any reason why TObject.Create isn't virtual?
Smasher
Probably because there's no need for it to be virtual. First off, virtual method calls only work with identical signatures, and most constructors take at least one parameter. Second, if you're using the factory pattern in this way, you probably want a descendant of a specific class anyway.
Mason Wheeler
+1  A: 

I would suggest you look into overriding the AfterConstruction method that's introduced by the TObject to make polymorphism like this work.

Each class definition can introduce a new constructor, with it's own set of parameters. A class variable only knows of the constructor if the base class it's a variable for. This is all possible because a constructor is neither virtual nor overridden. You could flag the constructor virtual on your base-class, and flag all descending classes override, which blocks the parameter list. (I think if you forget 'override' the compiler warns your new constructir hides the virtual one.)

Stijn Sanders
This won't solve the basic problem. Without a virtual constructor, he'll always end up creating TObject objects instead of the class he's looking for.
Mason Wheeler
Have you tried? given the code in the question and a TSomeOtherObject = class(TMyBaseObject), if you set a Someclass:TMyClass to TSomeOtherObject it should hold the in-memory pointer to the class data, including the overriden constructor. (I think, should try it myself...)
Stijn Sanders
Oh, well yeah, if he's using a more specific class variable. I thought you meant he could use a TClass and an AfterConstruction override to get the right behavior on anything descending from TObject.
Mason Wheeler
Yes, that is exactly what I meant by my first paragraph.
Stijn Sanders
+3  A: 

TObject.Create is not virtual, you need to declare ClassVar as a classtype of a class with a virtual constructor.

Ozan
A: 

Or descend it from TComponent, which already have a virtual constructor.

Fabricio Araujo
-1 ...Plus a lot of overhead that I don't really need. And it's confusing: "Components are persistent objects that have the following capabilities: IDE integration, ownership, streaming and filing, COM support" (cited from Delphi Help)...that's not really what I want here.
Smasher
Never had speed issues with TComponent. And for COM support, I know it does ref couting if the descendant is a wrapper for a COM object (VCLComObject field). And ownership is a bless when need to create subobjects which must be destroyed when comp dies. IDE integrations just means you can register it on pallete. So, I use TComponent as base for my objects, yes.
Fabricio Araujo
A class should only descend from TComponent if it is a component.
Gamecat
@GamecatA class should descend from TComponent if it uses some facility it provides and yes, I find the easyness to stream and the ownership very appealing to many of my classes. In this case, TComponent provides an virtual constructor chain - which is useful for the case in view. I have no dogmas here.
Fabricio Araujo