Re: Call virtual function in constructor
* Pavel:
Alf P. Steinbach wrote:
* Pavel:
It is sometimes useful to initialize different classes of a hierarchy
with the derived class-dependent code and then return back to the
base class constructor to execute some of its code again to avoid
duplicating that latter common code.
For example (this code will not work in C++ but the analogous code
will work in other programming languages (e.g. Java) and I do not see
any fundamental design flaws in this code).:
typedef std::map<std::string> ConnectionParameters;
class FooConnection {
protected:
virtual void init(const ConnectionParameters &pars) = 0;
public:
FooConnection(const ConnectionParameters &pars) {
init(pars);
validateConnection();
}
private:
void validateConnection()
throw(FooConnectionException /*defined elsewhere*/)
{
/* perform some uniform validation here, for example
some select from "FOO_MAIN_TABLE" */
}
};
class OracleFooConnection : public FooConnection {
protected:
void init(const ConnectionParameters &pars) {
// .. do Oracle-specific initialization
}
};
class MySqlFooConnection : public FooConnection {
protected:
void init(const ConnectionParameters & pars) {
// .. do MySql-specific initialization
}
};
"not ... any fundamental design flaws": heh, it is reportedly the most
common source of Java bugs.
The problem is that at the time the derived class' function
implementation is called, the derived class object has not yet been
initialized. Thus, member functions called from that function, or
even that function's own implementation, may very easily execute code
that relies on assumptions that have not yet been established. Apart
from run-time checking of array downcasts, which is also a strong
contender, I think that this is the most ugly type system breach in Java.
Thanks Alf!
I totally agree the objects should not be operated on until completely
initialized.
However, in this particular case we are talking about the initialization
itself and how conveniently the initialization can be performed and
factored onto the most relevant pieces of code, rather than about
regular operations. It can hardly be argued that initialization is
supposed to operate on uninitialized objects.
I think you meant to write "It can hardly be argued that initialization
is only supposed to operate on initialized objects".
That is true.
In C++ this is dealt with by constructors, which, as opposed to other
member functions, operate on not-yet-initialized objects.
The problem with Java's virtual calls from constructors can be restated
in these terms, that that mechanism does not deal with that problem.
Specifically, that it causes member functions other than constructors to
operate on not-yet-initialized objects (or more precisely, for Java, on
objects that have not yet had their class invariants established).
I also agree that every powerful feature of any language can be abused.
Well, both C++ and Java provide a rich variety of ways to abuse them
(arguably, C++ provides more) and one more is not going to change the
weather.
This way, you can perform all validation in the constructor, that is,
according to the best practices, and without duplicating the common
validation code in the derived class.
The above code is (unfortunately) common practice in Java, but it's
certainly not best practice.
Well, I just said that keeping all validations in the constructor agrees
with the best practices because the client code then "never" has an
access to an invalid object.
Well, nothing wrong with what you state here as follow-up.
What's wrong is the earlier "This way...", the virtual call (in Java and
some other languages) in the constructor invoking a function
implementation in a derived class.
That is not necessary in order to keep all validation in the
constructor, nor is it necessary in order to ensure that client code
only has access to valid objects.
It's an example of the exact opposite.
It is an anti-pattern.
I did not call the code above a pattern but "anti-pattern" seems little
"out of wack" to me :-). Why don't we try to refrain from tagging or
rubber-stamping each other's examples?
The above was a precise (well, OK, not that precise!) technical description.
Coming from rural Northern Norway, you know, fishermen and such, I can
assure you that when I resort to name calling, you'll know it... :-).
See <url: http://en.wikipedia.org/wiki/Anti-pattern> for a general
introduction to antipatterns.
Doing a Google search for a name of this particular antipattern didn't
turn up any hits.
However, since it is an antipattern it's called an antipattern here &
there on the net, e.g. <url:
http://mehranikoo.net/CS/archive/2006/11/28/InstanceConstructors.aspx>
and <url: http://debasishg.blogspot.com/2006_11_01_archive.html> (which
indicates the Eclipse can detect this antipattern automatically).
It is a C++ - specific feature that the implementation is not
required to construct the memory layout for the whole object of the
most derived class before calling the first constructor of a base
class (Java does it differently).
Of course, there are ways in C++ to cure this limitation, for example
by composition, where you can create a parallel Impl hierarchy which
does not validate, use member access control and "friends" to make
its object accessible only from within the primary hierarchy classes,
move database-specific init() into the parallel hierarchy and leave
the validateConnection() in the main hierarchy's base class.
Sometimes this added design complexity will be not much of a burden,
sometimes it will be. Personally I would prefer to have a choice not
to use it.
For ways to achieve dynamic binding during initialization (DBDI) in
C++, which also are more sane ways in Java, see FAQ item 23.6.
I think of that as "my" FAQ item since I convinced Marshall to include
it, but the text and exposition is of course Marshall's.
Unfortunately this happened much later than the treatment of clone
functions, so we're stuck with the term "virtual construction", at
least in the FAQ, referring to cloning, and the acronym "DBDI"
(Marshall's invention) for the techniques discussed in 23.6.
Well, I have read that FAQ entry carefully and I cannot agree it
suggests a clearly better solution to the original problem for which I
suggested that virtual calls in constructor would be useful. I stated
the problem in words in the beginning of my previous post, but for more
clarity let me illustrate it with a little client code snippet here.
Imagine that
#include <foo.h>
int main(int argc, char *argv[]) {
ConnectionParameters params(argv[1]);
/* this time, please assume that ConnectionParameters can be built from
a string argv[1] or a text file with this name -- seems reasonable */
OracleFooConnection fooConnection(params);
fooConnection.doSomethingUseful();
return 0;
}
is all our client is willing to write in his/her code. Fair and square.
Perhaps you may find my original sketch for that item more clear, <url:
http://home.no.net/alfps/cpp/faq_proposal/@virtual-functions.html#faq-20.7>,
although now I'm not sure what I meant at the end of the previous item
on that page (it was possibly some old VC6 problem) -- if I'd read
that written by someone else I'd probably used some very choice words...
For your particular case I'd choose what I in the page above call a
"part-creator", directly supporting the client code you show above.
It would go like (off the cuff)
class ConnectionFactory
{
public:
typedef ... Handle;
typedef ... Params;
Handle create( Params& const params ) const
{
Handle const h = doCreate( params );
isValid( h ) || throwX( "Burka!" );
return h;
}
virtual bool isValid( Handle const& h ) const = 0;
private:
virtual Handle doCreate( Params const& ) const = 0;
};
class FooConnection
{
public:
typedef ConnectionFactory::Handle Handle;
typedef ConnectionFactory::Params Params;
protected:
FooConnection(
Params const& params,
ConnectionFactory const& factory // Might default
)
: myHandleOnThings( factory.create( params ) )
{}
private:
Handle myHandleOnThings;
};
class OracleFooConnection : public FooConnection
{
public:
OracleFooConnection( Params const& params )
: FooConnection( params, Factory() )
{}
private:
class Factory: public ConnectionFactory
{
private:
virtual Handle doCreate( Params const& ) const { ... }
virtual bool isValid(
};
};
It splits things up very nicely in terms of responsibility, the
communication lines are very clear (as opposed to communication via
member variables, which is almost the same as global variables), and
there is no call of non-constructor function on uninitialized object.
Cheers, & hth.,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?