Yesterday I posted an observation on Google+, lamenting the lack of a compiler warning when code in an overridden method failed to call any inherited implementation. This simple oversight in an AfterConstruction override in a situation where the observed bug that arose (a memory leak) could just as easily have been the consequence of a more complex error on my part, caused me to spend a significant amount of time, hunting down the wrong bug.
Eric Grange suggested that AfterConstruction and BeforeDestruction was best forgotten. This was an interesting thought, but when I considered it I concluded that I had to disagree. Not only would it not help me avoid my problem had I ignored them, but doing so would merely have created new opportunities to introduce such time-wasting bugs.
AfterConstruction and BeforeDestruction fulfil a genuine need imho. Most especially I have found them of immense utility when creating frameworks. In a framework I find I often have a base class (or classes) from which further classes are intended to be derived, with (potentially) overridden constructors. Also quite often, the base class never-the-less requires some internal initialisation to reliably occur only after any overridden construction has been completed.
In the specific case that led to my initial post, the base class in question contained an interface reference counting bug fix I originally developed long before it was fixed in the VCL. Actually, it addresses a duo of bugs, one of which manifests during construction and the other during destruction. From memory (my Windows VM is not currently running so I can’t check the VCL source to be certain), the VCL now addresses the constructor side of things but not the destructor.
In any event, my base class incorporates a NewInstance override (effectively BeforeConstruction) and an accompanying AfterConstruction implementation that operated to ensure a positive reference count during the execution of those constructors. This addresses the situation where an object references itself using an interface during construction (e.g. passing a reference to itself to some other object which then discards it) resulting in the reference count “bouncing”, falling back to zero and thus destroying itself. A similar mechanism using BeforeDestruction ensures that “double-destruction” doesn’t occur if self is reference as an interface during destructor execution).
If AfterConstruction and BeforeDestruction didn’t exist (or you ignored them) then in such circumstances you would simply have to contrive your own exactly equivalent mechanism. This mechanism would still – by necessity – rely on virtual methods and overrides and so as far as I can see will still be vulnerable to the omission of calls to the inherited implementation.
But worse, without it being implemented in the base framework further steps would most likely be required to ensure that the alternate mechanism was correctly invoked. As far as I can see, ignoring the provided core mechanism and “rolling your own” would only compound the problem, increasing the potential to introduce a mistake rather than reducing it.
Setting aside the rights or wrongs of an AfterConstruction or BeforeDestruction mechanism in the specific, in the general case overriding a method and then neglecting to call “inherited” is a sufficiently rare occurrence that it would be worth warning about imho.
Furthermore, the compiler directive(s) required to then silence that warning would provide a self documenting and clear signal that the omission is indeed intentional, expected and indeed required (in those rare cases that it genuinely is).
-
Actually, no you really don’t need them, think out of the box here.
The VCL uses them to manage state f.i., which is then “if”-checked to prevent events getting potentially triggered during the destruction… if you needed a big signpost that screams “design issue”, look no further.
There are two different concepts really that are mishmashed and AfterConstruction/BeforeDestruction kludge their way to re-separate them: clean up the design by separating construction (memory management really) from initialization (putting values), and do similarly for destruction.
Here is your “equivalent mechanism”, except it’s NOT equivalent, as it cleaned up your design, and rather than working around a design issue of mishmashed concepts, you now have a design feature, that eliminates the need for magic state-dependent switches, grants your classes safe and simple pooling/streaming capability, facilitates DI & AOP, etc.The refcounting use of AfterConstruction is a hack to plug reference-counted memory management at a non-root object level of the class hierarchy, while making way for reference-counted classes that are not reference-counted… And that last kludge is the root of the “need”: without it, the refcounting could simply have taken place at the call site (as usual). And you “need” refcounted classes that aren’t refcounted in Delphi if you want to use interfaces on VCL classes, because interfaces are hard-tied to refcounting… another mishmash between an OO abstraction concept and a memory management technique.
Last use of AfterConstruction is a dubious convenience use for TThread, when CreateSuspend is false… not exactly and earth shattering use scenario either, first because it encourages mixing construction & initialization, second because at most, it saves one explicit “Start” at the expense of having an implicit boolean passed… doesn’t really make for more explicit code, does it?
So all in all, there just isn’t one non-kludgy reason to have those methods in Delphi.
-
The problem is that calling the inherited implementation is not mandatory in OOP. Although often it makes sense, there are implementations where a derived object may implement a very different behaviour that does not require a call to the parent one. Or for some reason the parent method may just raise an exception when called.
A warning could be “too much”, maybe an “hint” could be better, but there is not really nothing wrong “per se” avoiding an “inherited” call.
I got the habit of always starting an overridden method adding the proper “inherited” call, but that’s just a best practice. -
“If you implement a base class that requires post-constructor initialisation”
The only initialization that should happen post-construction in a good design is one that is context-dependent (ie. requires passing context-related parameters, references, etc.), and for those AfterConstruction is not appropriate (doesn’t accept context).
So you will never see a good design that “requires” such a context-independent post-constructor initialization…
“but have nothing what-so-ever to do with the original problem”
I strongly disagree here.
There are two cases in which calling the inherited constructor warrants a warning in a consistent fashion, that’s in a constructor/destructor. Delphi lacks that, sure, but even if it had one, it wouldn’t have helped you since you were using AfterConstruction, which being a kludge/hack is the last place where you would went it (whenever you’re using it, you know you’re doing something dirty in the first place).
Now, if you had complained that Delphi should warn about missing inherited calls (or multiple inherited calls btw) in the constructor or destructor, I would have agreed, but in AfterConstruction? No, that thing is a place for dirty hacks, it’s worth a warning on its own.
And I don’t agree about warning about inherited calls on overridden methods in general, because it wouldn’t make any sense half the time (think Paint, Walk, Quack, etc. actually it only makes sense if the subclass implements only a slight variation or adds to an already implemenetd behavior, but doesn’t define the behavior)
-
I agree, a compiler hint would be helpful. Perhaps a specific hint, which can be turned off.
Also agree with the need for AfterConstruction()/BeforeDestruction() in some circumstances.
Comments are now closed.


DelphiFeeds
7 comments