Call virtual function in constructor

A

Alf P. Steinbach

* Pavel:
Alf said:
* Pavel:
Alf P. Steinbach wrote:

In passing, check out[1] <url:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6596304>. :)

Yes, people make mistakes. JVM Specs is pretty clear about what is
supposed to happen and that is exactly what happens. The relevant
piece of the standard is easy to read and follow:
http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#19124
Hopefully when the "bug" comes to the developer, they will close it
instead of "fixing" it.

I think it clearly illustrates the dangers of those virtual calls from
constructors.

For the problem is that code is maintained, not just originally
written (about 80% of coding work is maintainance, as I recall the
statistics).

When a maintainance programmer (which might be oneself) comes along
and e.g. adds an initializer to a class member, or falls for the
temptation to let the downcalled function do something with or to the
derived class' members, or adds a call there to a function that
someone else will in turn make do something with or to the derived
class' members, perhaps even in a class derived from the derived one,
where the context of the call is not at all clear, then presto, a new
bug or set of bugs.

I guess it is the problem with any framework, isn't it? A framework (be
it a Template Method, Factory or operator overloading) has to rely on
the maintenance programmer's using rather than abusing it. I am not
aware of a satisfactory protection against the concrete factory's
create() method returning a random integer casted to a pointer, the
semantics of two Template Method's concrete operations being swapped
(which, incidentally, happens a lot) or an overloaded binary operator*
for some Matrix class calculating pair-wise products instead of matrix
product (or other way around).

Certainly a good framework must let its *benevolent* user as clear and
readily available clue of what's expected from him/her as possible. My
opinion is that the name of init() virtual function would give just
right a clue and you disagree, you believe that the Holder/Factory
machinery is less error-prone and reduces maintenance costs and I
disagree. I really doubt we can come to an agreement on this so I
suggest that we disengage.

Hm, you throw in a number of apparently new arguments and then
effectively say "I'm going, bye".

Most of the problems you note above are difficult to protect against no
matter what general solution is adopted, i.e. apply equally. Like
returning a square root from a logarithm function -- which could perhaps
be protected against with judicial use of Design By Contract, but AFAIK
only Eiffel has proper DBC support[1]. The problem you note with the
Template Pattern (which is what I think you mean by Template Method),
which you say "happens a lot", does however require an example, because
the Template Pattern is essentially a means to impose the restrictions
one deems necessary -- and it seems you're saying that it doesn't.

Anyway, it seems the argument is that there are things one can't protect
against with reasonable effort, and since one can't in practice protect
against all cases, one should not do the easy things to protect against
more likely mishaps either. Therefore, one should not invest effort in
restricting usage to only valid usage, which is what using static type
checking is all about, and what C++'s type safety for construction is
all about. I most emphatically disagree that that effort is wasted.

With proper use of static typing, e.g. as with a "parts constructor",
you have the very strong clue that code that breaks the assumptions &
rules captured by the types, doesn't compile.

With virtual calls down to derived class from a constructor, you have no
clue other than documentation and perhaps a naming convention. The code
blowing up comes as a surprise, as illustrated by the Java bug report
referred to above (top). But much worse, it allows you to build a huge
edifice resting on invalid assumptions, before the blow-up happens.


Cheers,

- Alf

Notes:
[1] D has a little. The C++ DBC proposal stranded on something, I'm not
sure what. I seem to recall it had to do with the difficulty of
definining exactly what constitutes an external interface of a class,
e.g. the problem of callbacks during a temporarily broken class
invariant. Unless Google is tricking me it can be found at <url:
http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2004/n1613.pdf>.
 
A

Alf P. Steinbach

* Alf P. Steinbach:
> Notes:
[1] D has a little [DBC]. The C++ DBC proposal stranded on something, I'm not
sure what. I seem to recall it had to do with the difficulty of
definining exactly what constitutes an external interface of a class,
e.g. the problem of callbacks during a temporarily broken class
invariant. Unless Google is tricking me it can be found at <url:
http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2004/n1613.pdf>.

Funny how Google works, I just stumbled over a revision to the above
document, and it's in HTML rather than PDF, so more accessible, <url:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2004/n1669.html>.
Also, has my name in it... :)

Cheers, & hth.,

- Alf
 
P

Pavel

Alf said:
* Pavel:
Alf said:
* Pavel:
Alf P. Steinbach wrote:

In passing, check out[1] <url:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6596304>. :)

Yes, people make mistakes. JVM Specs is pretty clear about what is
supposed to happen and that is exactly what happens. The relevant
piece of the standard is easy to read and follow:
http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#19124


Hopefully when the "bug" comes to the developer, they will close it
instead of "fixing" it.

I think it clearly illustrates the dangers of those virtual calls
from constructors.

For the problem is that code is maintained, not just originally
written (about 80% of coding work is maintainance, as I recall the
statistics).

When a maintainance programmer (which might be oneself) comes along
and e.g. adds an initializer to a class member, or falls for the
temptation to let the downcalled function do something with or to the
derived class' members, or adds a call there to a function that
someone else will in turn make do something with or to the derived
class' members, perhaps even in a class derived from the derived one,
where the context of the call is not at all clear, then presto, a new
bug or set of bugs.

I guess it is the problem with any framework, isn't it? A framework
(be it a Template Method, Factory or operator overloading) has to rely
on the maintenance programmer's using rather than abusing it. I am not
aware of a satisfactory protection against the concrete factory's
create() method returning a random integer casted to a pointer, the
semantics of two Template Method's concrete operations being swapped
(which, incidentally, happens a lot) or an overloaded binary operator*
for some Matrix class calculating pair-wise products instead of matrix
product (or other way around).

Certainly a good framework must let its *benevolent* user as clear and
readily available clue of what's expected from him/her as possible. My
opinion is that the name of init() virtual function would give just
right a clue and you disagree, you believe that the Holder/Factory
machinery is less error-prone and reduces maintenance costs and I
disagree. I really doubt we can come to an agreement on this so I
suggest that we disengage.

Hm, you throw in a number of apparently new arguments and then
effectively say "I'm going, bye".
No, I just tried to summarize, from my perspective, of course, where we
standed assuming you would agree we have different personal estimates of
the same technical issue. I was apparently wrong -- you believe that
there can be a complete or near-complete solution of
"construct-by-template" problem (I had to name it somehow to refer,
please suggest your name if you disagree, I thought also of
"construction with subcontractors" but it just does not sound right).

I did not see a solution that would "make me salivating", in Buffett's
words, whether yours or mine own in C++. In Java, I would definitely go
the route you deprecate (use virtuals) and not because I am lazy (I
certainly am, but this would not be the reason). I already mentioned
what my personal experience makes me conclude about the maintenance
costs and error potentials of more complex solution and what similar
approaches did to the systems I had to work. I assumed that your
experience tells you otherwise but we can "agree" that it is our
different background/experience makes us disagree and stop at that.

I might have thrown some arguments but more to illustrate my viewpoint
than to defeat yours. I tried to show that, in my view, there is always
a balance between complexity and protection; that complex protection is
a problem in itself because the code is read by live people and the more
they have to read (of more elaborate code, maybe) the less is their
attention -- basically, that simplicity pays off. And I believe the
"true" balance independent on the team's experience, background and
areas of concentration simply does not exist.

I believe that when, after 2-3 months after writing
"OrcaleFooConnection" or later the new, say, "MySqlFooConnection" will
need to be added (or new validation common for all connection, say
"select version from auxilliary_foo_table"), the simpler design will
show wins in both time-to-market and lower probability of error and it
is highly unlikely someone will start reading uninitialized class
variables or do a similar thing in "init()" virtual method -- more
unlikely than, with the more complex approach, someone misunderstands
our "clear communication lines" or will not even care (or have time) to
delve in and will write some "clever shortcut" or "bad hack" or will
start all over from scratch. This what my background/experience tells
me but I understand and accept that yours may tells you exactly the
opposite. So I suggested to disengage because my feeling it is a
hopeless task to try to change each other's perceptions (and I do
believe we are arguing about perceptions on which each of us tries to
build the balance I mentioned before).

Most of the problems you note above are difficult to protect against no
matter what general solution is adopted, i.e. apply equally. Like
returning a square root from a logarithm function -- which could perhaps
be protected against with judicial use of Design By Contract, but AFAIK
only Eiffel has proper DBC support[1]. The problem you note with the
Template Pattern (which is what I think you mean by Template Method),
which you say "happens a lot", does however require an example, because
the Template Pattern is essentially a means to impose the restrictions
one deems necessary -- and it seems you're saying that it doesn't.
No, template method "Defines the skeleton of an algorithm in an
operation, deferring some steps to subclasses" (GoF). No elaborate
design can protect from the programmer's misunderstanding of the
semantics of the steps (say, implementing some responsibilities of step1
in step2 or vice versa versus what the template method's author
intentions). Say, one uses STL's find (it is not exactly template method
in strict sense, because iterators are not implemented via inheritance,
but the sense stays) on his/her own special iterators and these
iterators, while syntactically satisfying all algorithm's requirements
(in our case, STL requirements to iterators of some type), are not doing
what's expected in ++ or doing too much (like have some side effects).
Many invariants of a template method, implied or expressed, can be
broken this way. Iterators are well known abstractions so you may argue
nobody will screw up this way, but this argument would be similar to
what I said about init() -- that it is a well-known concept. Less-known
concept (again, it all depends on a personal background) would produce a
more error-prone situation. For example, in the GoF's original example
(the template method for opening a document in GUI application):

void Application::OpenDocument (const char* name) {
if (!CanOpenDocument(name)) {
// cannot handle this document
return;
}
Document* doc = DoCreateDocument();
if (doc) {
_docs->AddDocument(doc);
AboutToOpenDocument(doc);
doc->Open();
doc->DoRead();
}
}

How would one implement CanOpenDocument()? One way of jumping into it
(meaning, without reading an OpenDocument template specification -- and
you stated that the documentation/naming convention is not good enough)
is actually open document, being guided by the otherwise good principle
"do, don't ask". Now, on some platforms, you will open a file for
exclusive access in CanOpenDocument() and then doc->Open will always
fail. On other platforms you may only create a resource leak this way.

Does it all mean the "Template method" pattern itself is an
anti-pattern? I will not even try to answer but hopefully we now both
agree it can be misused.

BTW don't ask me where the "name" is used in opening the document in the
above example -- I did not write it, it's all them, GoF. As I said
before, people make mistakes.. that what they do.

Anyway, it seems the argument is that there are things one can't protect
against with reasonable effort, and since one can't in practice protect
against all cases, one should not do the easy things to protect against
more likely mishaps either. Therefore, one should not invest effort in
restricting usage to only valid usage, which is what using static type
checking is all about, and what C++'s type safety for construction is
all about. I most emphatically disagree that that effort is wasted.
No, what I am saying that, because one can't protect against all cases
(on practice as well as in theory, because even in theory having the
model of all possible errors to protect against means you need to
express the solution before writing the protected solution -- and then
who is going to protect that first expression of the solution -- you've
got chicken-and-egg. In other words, any framework is just a skeleton of
a solution and the user is always free to put on that skeleton either
right or wrong flesh. As you are trying to provide more protective bones
on the skeleton, it becomes more complex and also, you make more
assumptions about the future flesh -- by that somewhat hurting the
original purpose of the framework), there is a need for a compromise.
Further I am saying the optimal compromise or balance depends on many
variables, many of which are subjective (like the development team) so
the balance itself is subjective.
With proper use of static typing, e.g. as with a "parts constructor",
you have the very strong clue that code that breaks the assumptions &
rules captured by the types, doesn't compile.
Yes, but I need to spend more time and brain space to understand these
assumptions and I will borrow this time and brain space against the time
and brains space I need to devote to my business (in our case,
understanding how to initialize the XXX-specific XXXFooConnection). So
the chances are higher that I end up with a mistake in my domain area.
And what I am getting in exchange? The guarantee I will not have a
chance to make a mistake in init() method by doing something else than
initialization there? Does not sound like a good deal to me -- I am
pretty sure I will write init() that initializes..
With virtual calls down to derived class from a constructor, you have no
clue other than documentation and perhaps a naming convention.
As you probably already understood, the documentation and, especially,
naming convention sound very good to me -- I would take these as enough.
The code
blowing up comes as a surprise, as illustrated by the Java bug report
referred to above (top).
Not sure how code blowing is illustrated by the "bug" report -- the
illustrating program is pretty minimal, very good bug report, actually,
except for the person is mistaken about the expected result.

But much worse, it allows you to build a huge
edifice resting on invalid assumptions, before the blow-up happens.
Well, if a blow-up happens, it will be easily fixed (in the bug report
example, the programmer just has to remove one single explicit
initializer to null -- which should not have been there at the first
place in a Java program. Plus initializing same variable twice into
different values is rarely a good idea, in any languages. In real
program would tell me the author did not know what s/he wanted or there
was no effective management -- and then they have a bigger problem then
virtual methods). And the chances of finding a bug in smaller code are
always higher than in larger code -- given same time allotted for QA.
Cheers,

- Alf

Notes:
[1] D has a little. The C++ DBC proposal stranded on something, I'm not
sure what. I seem to recall it had to do with the difficulty of
definining exactly what constitutes an external interface of a class,
e.g. the problem of callbacks during a temporarily broken class
invariant. Unless Google is tricking me it can be found at <url:
http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2004/n1613.pdf>.
Regards,
-Pavel
 
P

Pavel

Alf said:
* Alf P. Steinbach:
Notes:
[1] D has a little [DBC]. The C++ DBC proposal stranded on something,
I'm not sure what. I seem to recall it had to do with the difficulty
of definining exactly what constitutes an external interface of a
class, e.g. the problem of callbacks during a temporarily broken class
invariant. Unless Google is tricking me it can be found at <url:
http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2004/n1613.pdf>.

Funny how Google works, I just stumbled over a revision to the above
document, and it's in HTML rather than PDF, so more accessible, <url:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2004/n1669.html>.
Also, has my name in it... :)

Cheers, & hth.,

- Alf
Interesting. (I am quite ignorant in DBC so the following may be all
wrong): the preconditions seem like aspects to me -- you can write them
in one place (e.g. first declaration of a virtual function) and they
will be checked in many places, e.g. in all overrides. Now, with
aspects, their problem is their advantage -- non-local effect. And of
course the effect from an error in precondition itself becomes more
profound, too.

Only time can say how practical these things will be -- whether they
will take off or die, save or waste time and reduce or induce bugs. I
believe how they interact with the human nature will be more important
than how theoretically complete protection they provide. Good or bad
tools will play a big role, too.

Regards
-Pavel
 
R

Ron Natalie

Ian said:
Yes, you simply should no do it. Derived classes haven't been
constructed when the base class constructor is called.

It's perfectly safe. The dynamic type of the object during
construction is the type of the constructor being run. It won't touch
the derived stuff.
Try making the
function pure in the base class and see what happens...

That would be undefiend behavior. "Trying" things isn't predictable.
 
I

Ian Collins

Ron said:
It's perfectly safe. The dynamic type of the object during
construction is the type of the constructor being run. It won't touch
the derived stuff.
It may be safe, but it almost certainly does not give the expected
result. The advice still stands. don't do it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,588
Members
45,092
Latest member
vinaykumarnevatia1

Latest Threads

Top