3

Suppose in Delphi you have these classes:

type
   TClass1 = class
     public
      constructor Create;
   end;

   TClass2 = class(TClass1)
     public
      constructor Create;
   end;

   TClass3 = class(TClass2)
     public
      constructor Create;
   end;

Note that TClass1.Create is not virtual, and that TClass2 and TClass3 declare a constructor which is not virtual.

Suppose that I want to invoke TClass1's Create constructor-method, from within TClass3.Create, but not invoke the constructor-code in TClass2.Create? Is this possible within the language without recourse to RTTI?

I don't think there is such a syntax, but what I want is:

constructor TClass3.Create;
begin
  super inherited Create; // Invoke TClass1.Create
end;

The closest I can get is this which compiles but just leaks an object, as it's doing a separate TClass1.Create construction.

constructor TClass3.Create;
begin
   TClass1.Create; // returns new TClass1, discards and leaks it.
   // other initialization here.
end;

It also seems to me that the code TClass1.Create invocation within TClass3.Create compiles, I cannot call it correct, it is wrong because it leaks an object. What is the correct right way to do it?

Update Note that David's answer works for a class hiearchy without virtual constructors, only, as I originally asked. His answer would not work in your code, if you had virtual constructors and TClass2 and TClass3 overrode them. If I had asked the above question with virtual constructors (or a virtual method that is not a constructor) the answer would be "you can't do it at all, except by really gross Virtual Method Table hacks". Also note that the linked "possible duplicate" is not a duplicate because the answer changes when you add/subtract virtual methods from the situation.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • I'm sure you can circumvent the need for such a construct by restructuring the class hierarchy a bit, maybe add a common base class for `TClass2`and `TClass3` deriving from `TClass1` or something like that. – Uli Gerhardt Jan 14 '14 at 20:31
  • 1
    If it was all my code, I would. I try to avoid rewriting third party component libraries, though. – Warren P Jan 14 '14 at 20:32
  • 1
    I think a call like `TSomeClass.Create` allocates memory for an instance and then executes the constructor body while calls like `inherited Create` or `SomeInstance.Create` only execute the body on an already existing instance, so `TClass1.Create;` won't work AFAICT. – Uli Gerhardt Jan 14 '14 at 20:36
  • If I understand you right `TClass1` and `TClass2` are third party, while you are writing `TClass3`. Correct? – Uli Gerhardt Jan 14 '14 at 20:39
  • don't know if [this answer](http://stackoverflow.com/a/4663197/800214) can help? – whosrdaddy Jan 14 '14 at 20:57
  • That is helpful. I think I'll stick with my other ugly hack (destroy and recreate private field objects) instead of avoiding the Constructor invocation. – Warren P Jan 14 '14 at 21:22
  • possible duplicate of [Delphi: How to call inherited inherited ancestor?](http://stackoverflow.com/questions/4662744/delphi-how-to-call-inherited-inherited-ancestor) – Rob Kennedy Jan 14 '14 at 22:10
  • notice the difference in being virtual and non-virtual. The "duplicate" is about virtual. This question is about non-virtual. The distinction is important, because there is a way to call a parent's method two or more classes up, by using a class-cast, but that only works when it's NOT virtual. Thus this provides an interesting searchable fact. Please do not close this question having failed to understand these details. – Warren P Jan 15 '14 at 14:14

2 Answers2

2

There is no syntactical support for skipping a layer of the inheritance hierarchy. The only way you can do what you want is like this:

TClass1(Self).Create;

A complete example program to demonstrate:

type
  TClass1 = class
    constructor Create;
  end;

  TClass2 = class(TClass1)
    constructor Create;
  end;

  TClass3 = class(TClass2)
    constructor Create;
  end;

constructor TClass1.Create;
begin
  Writeln('TClass1');
end;

constructor TClass2.Create;
begin
  inherited;
  Writeln('TClass2');
end;

constructor TClass3.Create;
begin
  TClass1(Self).Create;
  Writeln('TClass3');
end;

begin
  TClass3.Create;
  Readln;
end.

Output

TClass1
TClass3
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Works fine here. I'm using your classes from the question that don't have virtual constructors. Of course, the real code, that which I cannot see, does use virtual constructors, doesn't it? ;-) – David Heffernan Jan 14 '14 at 21:24
  • Oh rats. In my real code, the constructor in TClass2 and TClass3 are override, and TClass1.Create is virtual. – Warren P Jan 14 '14 at 21:28
  • This won't work with virtual constructors - that is what leads to the stack overflow. (Oops! too slow!) – Gerry Coll Jan 14 '14 at 21:28
  • @GerryColl Indeed. Note the lack of virtual constructors in the question. – David Heffernan Jan 14 '14 at 21:29
  • My question was missing that, and it turns out that in my case, I need it to work for virtual constructors. I apologize for updating it to break your nifty answer. – Warren P Jan 14 '14 at 21:30
  • @WarrenP If you want to ask a different question, make it so. But please don't edit this one and thus completely invalidate all the time and effort I put in. – David Heffernan Jan 14 '14 at 21:30
  • 1
    No. Just ask a new question. Don't get mixed up between the question that you asked, and the problem you need to solve. They are different things. – David Heffernan Jan 14 '14 at 21:32
  • I think your answer, and my question are now a good match for each other. I accepted this answer. – Warren P Jan 14 '14 at 21:35
  • 1
    If I asked this as a virtual-only question, then the answer linked here would be the duplicate.... http://stackoverflow.com/questions/4662744/delphi-how-to-call-inherited-inherited-ancestor?lq=1 – Warren P Jan 14 '14 at 21:53
  • @WarrenP If you asked this as a virtual-only question, then the answers posted there don't answer the question. :) The `inherited Foo;` syntax works in class helpers, and generates a non-virtual call to the inherited function. The method defined in the class helper can then be called even on derived classes. –  Jan 14 '14 at 22:04
  • They don't answer the question in the positive, they answer it in the negative, and the fact remains that you can not easily override virtual method behaviour. Casting has no effect, and `inherited Method(param)` syntax permits you to go one and only one level upwards. If a constraint exists where you should not modify a higher level class, perhaps it is TListBox, or perhaps it is a third party library of devastating complexity, then you need to know your choices. – Warren P Jan 15 '14 at 14:20
2

While you should not do this you actually can achieve it with inline assembly code:

constructor TClass3.Create;
begin
  asm
    mov eax, Self
    call TClass1.Create;
  end;
  Writeln('TClass3');
end;

But keep in mind that this is actually different from a theoretical super inherited (which would skip one inheritance level) while this just calls the said method. So if you introduce another inheritance level TClass2b between TClass2 and TClass3 it will skip that aswell.

Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • That's really cool and kind of scary. Agree that you should not. But it is nice to know how you would. This has the odd and interesting property of bypassing the VMT/virtual-methods, if there were any, say. – Warren P Jan 16 '14 at 15:25
  • Actually this is the code that the compiler produces for an inherited in this case TClass2.Create of course :) – Stefan Glienke Jan 17 '14 at 06:44