Topic: Post-constructors and pre-destructors
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Wed, 28 Dec 1994 14:11:55 GMT Raw View
tob@world.std.com (Tom O Breton) writes:
>Very briefly, a static member function does not automatically know about
>any particular instance. Therefore it can't exhibit virtual behavior,
>because it has no referent _to be virtual in regard to_.
It's true that in C++ static member functions can't be virtual, but
this is an explicit restriction of C++, not a logical necessity. Some
calls to static member functions do not specify a particular instance,
but others do. There is no particular reason (except perhaps for ease
of implementation?) to disallow static virtual functions, which could
certainly exhibit virtual behaviour if called via a pointer or
reference.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: tob@world.std.com (Tom O Breton)
Date: Wed, 28 Dec 1994 19:56:52 GMT Raw View
fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
> It's true that in C++ static member functions can't be virtual, but
> this is an explicit restriction of C++, not a logical necessity. Some
> calls to static member functions do not specify a particular instance,
> but others do. There is no particular reason (except perhaps for ease
Well, yes, if a static gets a pointer or ref to a particular instance it
could (in theory, not in C++) dispatch polymorphically from that
instance. But if it does that it's just duplicating ordinary member
functions. Changing the syntax slightly but a forwarding function can do
that.
So while it's not a logical neccessity, there's no reason for it even in
theory.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: linus@step.polymtl.ca (Linh Dang)
Date: 29 Dec 1994 05:40:40 GMT Raw View
Fergus Henderson (fjh@munta.cs.mu.OZ.AU) wrote:
: tob@world.std.com (Tom O Breton) writes:
: >Very briefly, a static member function does not automatically know about
: >any particular instance. Therefore it can't exhibit virtual behavior,
: >because it has no referent _to be virtual in regard to_.
: It's true that in C++ static member functions can't be virtual, but
And why can't static member functions be const ???
If static data members define the state of the `class'
and static member funcs define its behaviour
then a const static member func would be one which
does not modifie the state of the `class'.
: this is an explicit restriction of C++, not a logical necessity. Some
: calls to static member functions do not specify a particular instance,
: but others do. There is no particular reason (except perhaps for ease
: of implementation?) to disallow static virtual functions, which could
: certainly exhibit virtual behaviour if called via a pointer or
: reference.
I'd rather have const static member funcs than virtual smf.
IMHO, smf was designed help modularity and not polymorphism.
L.D.
: --
: Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 30 Dec 1994 00:06:39 GMT Raw View
In article <9436301.10775@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
>tob@world.std.com (Tom O Breton) writes:
>
>>Very briefly, a static member function does not automatically know about
>>any particular instance. Therefore it can't exhibit virtual behavior,
>>because it has no referent _to be virtual in regard to_.
>
>It's true that in C++ static member functions can't be virtual, but
>this is an explicit restriction of C++, not a logical necessity. Some
>calls to static member functions do not specify a particular instance,
>but others do. There is no particular reason (except perhaps for ease
>of implementation?) to disallow static virtual functions, which could
>certainly exhibit virtual behaviour if called via a pointer or
>reference.
Sure, there is an excellent reason for disallowing static
virtual functions. The committee hates extensions to the language
which can be trivially implemented by end users but whose use is
not a major problem.
struct X {
static void f_();
virtual void f() { f_(); } // static virtual
};
struct Y : X {
static void f_();
virtual void f() { f_(); } // static virtual
};
The fact that you can already do this by a simple mechanical
idiom is a good argument for _not_ including it in the
language -- even though -- no, rather _because_ -- it proves the idea
is sound based on existing mechanisms.
A much more important question, IMHO, is how one
might _automate_ such an idiom ( to make its use more obvious
as a design technique and to reduce RSI). It is clear you can use a macro
for this purpose, and it is not clear to me you could use a template.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: ccwf@russel.klab.caltech.edu (Charles Fu)
Date: 30 Dec 1994 02:39:03 GMT Raw View
In article <D1LKB4.IH5@ucc.su.oz.au>,
> Sure, there is an excellent reason for disallowing static
>virtual functions. The committee hates extensions to the language
>which can be trivially implemented by end users but whose use is
>not a major problem.
>
> struct X {
> static void f_();
> virtual void f() { f_(); } // static virtual
> };
> ...
With the minor blemish that now one can no longer do
X::f();
Of course, would anyone _want_ to do that in a program needing static
virtual functions?
-ccwf
Author: tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa)
Date: 30 Dec 1994 10:13:17 GMT Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
[...]
: Notice that for this to work, "rectangle" itself should be ABSTRACT.
: In general, the classes from which you derive should be abstract.
[...]
Does this mean that it is wrong to use inheritance for reusing code?
Tommi H yn l nmaa (7-bit ASCII: H|yn{l{nmaa)
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 30 Dec 1994 18:54:17 GMT Raw View
In article <3e0mft$2jv@tethys.otol.fi> tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
>John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
>[...]
>: Notice that for this to work, "rectangle" itself should be ABSTRACT.
>: In general, the classes from which you derive should be abstract.
>[...]
>
>Does this mean that it is wrong to use inheritance for reusing code?
No, not at all. It is, I think, best not to reuse _data_.
But abstract bases may still have impure member functions:
struct image {
virtual Colour getpixel(int x,int y)const=0;
virtual void getrow(int x, int y, int len, Colour *r)
{
for(int dx=0; dx<len; ++dx)
r[dx]=getpixel(x+dx,y);
}
};
struct arrayImage : image {
Colour *data;
int width;
..
Colour getpixel(int x, int y) const {
return data[x+y*width];
}
};
You do not need to rewrite "getrow" here -- it works automatically,
even though it is a bit slow. You can, in this case, choose to
override "getrow" to be more efficient: specialisation.
Note specialisations may apply even to _abstractions_:
class Matrix {
..
virtual void transpose() { for.... }
};
class SymmetricMatrix {
void transpose(){}
};
Some routine can be specialised in a derived class and some cannot.
Those that can may, or may not, be specialised depending on
engineering tradeoffs. Often, the results of profiling, for example,
are useful for deciding _what_ to specialise and what to reuse.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: schuenem@Informatik.TU-Muenchen.DE (Ulf Schuenemann)
Date: 30 Dec 1994 19:48:10 GMT Raw View
In article <9436423.13701@mulga.cs.mu.OZ.AU>, fjh@munta.cs.mu.OZ.AU (Fergus Henderson) writes:
[..]
|> what hidden argument(s) get passed:
|> ordinary fn pointer to object
|> static fn nothing
|> virtual fn pointer to object and (hence) pointer to vtable
|> static virtual fn pointer to vtable
^^^^^^^^^^^^^^^^^(*)
[..]
IMO (*) would introduce a new kind of functions that haven't got a 'this'
but that (themself in their body) dispatch calls to other virtual methods
(in this case only static virtual fn) by the vtable.
call virtual methods | has a this | has no this
-------------------------------------------------------------
by vtable | ordinary/static fn | (*)
by static link | - | static fn
I think this would make things too complicated. I would prefere
the same semantics of the implementation (interior) of static fns,
for both ordinary and virtual fns. I.e.:
- no 'this'
- calls to other memberfns are linked statically
It would be clearer if these two features of a memberfn could
only come together or not at all.
Compare it to nonstatic fn: There is no difference in how ordinary
and virtual fns call other memberfns.
IMHO virtuallity of a memberfn should only make a difference in how
you call it, how you call this one memberfn, and not how itself
calls other memberfns.
Ulf Schuenemann
--------------------------------------------------------------------
Ulf Sch nemann
Institut f r Informatik, Technische Universit t M nchen.
email: schuenem@informatik.tu-muenchen.de
Author: guthrie@miu.edu
Date: Sun, 01 Jan 95 22:02:19 CDT Raw View
Once: <fjh@munta.cs.mu.OZ.AU> writes:
> >Very briefly, a static member function does not automatically know
about
> >any particular instance. Therefore it can't exhibit virtual
behavior,
> >because it has no referent _to be virtual in regard to_.
>
> It's true that in C++ static member functions can't be virtual, but
> this is an explicit restriction of C++, not a logical necessity.
> Some calls to static member functions do not specify a particular
> instance, but others do.
-- I always believed that an object based reference to any static was
misleading, and should be avoided; perhaps even disallowed!
>There is no particular reason (except perhaps for ease
> of implementation?) to disallow static virtual functions, which could
> certainly exhibit virtual behaviour if called via a pointer or
> reference.
>
?? Then what is "static" about it?
In what sense is it class-wide, or any different than a normal member
virtual function?
Of course one can design anything; but something that is static (in the
current sense) if accessed through one syntax, and object-ive when
accessed through another, seems confusing. I can't think of a design
idea which it implements.
Can you give any useful examples?
(If I understand your point correctly!)
Thanks.
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Mon, 2 Jan 1995 20:54:10 GMT Raw View
In article <3e7u7e$nat@news.iastate.edu> guthrie@miu.edu writes:
>
>Once: <fjh@munta.cs.mu.OZ.AU> writes:
>>
>> It's true that in C++ static member functions can't be virtual, but
>> this is an explicit restriction of C++, not a logical necessity.
>> Some calls to static member functions do not specify a particular
>> instance, but others do.
>-- I always believed that an object based reference to any static was
>misleading, and should be avoided; perhaps even disallowed!
It makes sense for "transparency". Some member functions
may require access to the object initially, then get changed so they
no longer need to access any non-static data members.
For example, a graphics device driver object may contain
access control information and also a device address. Some functions
may simply query the device via the address.
A DOS implementation might change the device address to
a static data member (or even hard code the address) because a PC
has only one graphics card. Then the member function can be made
static, which permits it to be called without an object. But existing
code ported from Unix will still operate correctly: it would be
a pain to change every
object->member();
to
Graphics::member();
(and back again if you change your mind :-)
>
>>There is no particular reason (except perhaps for ease
>> of implementation?) to disallow static virtual functions, which could
>> certainly exhibit virtual behaviour if called via a pointer or
>> reference.
>>
>?? Then what is "static" about it?
> In what sense is it class-wide, or any different than a normal member
>virtual function?
Virtual dispatch may be useful even if no data of a particular
object is to be accessed. For example a virtual function returning
run time type information needs to know what type to return the information
for, but this information is the same for every instance of that type.
The principle advantage in that case of saying "virtual static"
is that it is clear that the function is sensitive to type but not
instance state.
[I doubt this is worth a language change]
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: schlie@taurus.ece.cmu.edu (Paul W. Schlie)
Date: 3 Jan 1995 00:10:28 GMT Raw View
IMHO I disagree, virtual static is very usefull for the reasons stated
and should be part of the formal spec.
Author: swillden@fcom.cc.utah.edu (Shawn Willden)
Date: 3 Jan 1995 16:00:52 GMT Raw View
Tres Seaver (tseaver@sam.neosoft.com) wrote:
: >If static data members define the state of the `class'
: >and static member funcs define its behaviour
: >then a const static member func would be one which
: >does not modifie the state of the `class'.
: Briefly, because tagging a member function 'const' is a signal to the compiler
: to use that version on const instances of the class -- how are you going to
: have a "const" version of the class data?
Well, what is `class data'? If by class data you mean the static data
members of the class, I don't see a problem.
class Foo { static int a; static int func() const; }
In Foo::func, the type of `a' would be const int.
--
Shawn Willden
swillden@icarus.weber.edu
Author: tseaver@sam.neosoft.com (Tres Seaver)
Date: Tue, 3 Jan 1995 09:27:12 Raw View
In article <3dti4o$9gb@charles.cdec.polymtl.ca> linus@step.polymtl.ca (Linh Dang) writes:
>From: linus@step.polymtl.ca (Linh Dang)
>Subject: Re: Post-constructors and pre-destructors
>Date: 29 Dec 1994 05:40:40 GMT
>Fergus Henderson (fjh@munta.cs.mu.OZ.AU) wrote:
>: tob@world.std.com (Tom O Breton) writes:
>: >Very briefly, a static member function does not automatically know about
>: >any particular instance. Therefore it can't exhibit virtual behavior,
>: >because it has no referent _to be virtual in regard to_.
>: It's true that in C++ static member functions can't be virtual, but
>And why can't static member functions be const ???
>If static data members define the state of the `class'
>and static member funcs define its behaviour
>then a const static member func would be one which
>does not modifie the state of the `class'.
Briefly, because tagging a member function 'const' is a signal to the compiler
to use that version on const instances of the class -- how are you going to
have a "const" version of the class data?
Const static member functions would aid readablility / documentation, but
would not be useful in enforcing "constness" to the extent that normal const
members are.
Regards,
Tres Seaver, tseaver@neosoft.com
MACRO Enterprises, Inc., (713) 827-7273
Houston, Texas
Author: tseaver@sam.neosoft.com (Tres Seaver)
Date: Tue, 3 Jan 1995 13:00:50 Raw View
In article <3ebsbk$r3u@news.cc.utah.edu> swillden@fcom.cc.utah.edu (Shawn Willden) writes:
>From: swillden@fcom.cc.utah.edu (Shawn Willden)
>Subject: Re: Post-constructors and pre-destructors
>Date: 3 Jan 1995 16:00:52 GMT
>Tres Seaver (tseaver@sam.neosoft.com) wrote:
>: >If static data members define the state of the `class'
>: >and static member funcs define its behaviour
>: >then a const static member func would be one which
>: >does not modifie the state of the `class'.
>: Briefly, because tagging a member function 'const' is a signal to the compiler
>: to use that version on const instances of the class -- how are you going to
>: have a "const" version of the class data?
>Well, what is `class data'? If by class data you mean the static data
>members of the class, I don't see a problem.
>class Foo { static int a; static int func() const; }
>In Foo::func, the type of `a' would be const int.
>--
>Shawn Willden
>swillden@icarus.weber.edu
But the point is that there is only _one_ copy of Foo:a, so you don't
need a separate version of Foo::func() for the non-existant 'const' copies.
Tagging Foo::func as const thus doesn't buy any type safety, but only provides
a promise to clients of the class not to modify (static) members. There are
no cases in which the compiler would allow you to use the const version of
Foo::func() when it would not also allow a non-const version.
Thus, static const member functions might be useful for notational purposes,
but because they do not have the same semantics as normal const member
functions, they could be misleading.
It would be just as effective to publish a comment in the class declaration:
class Foo
{
static int a;
static void func(); // Does not modify static members!
};
except that the compiler could help catch it if you did the following:
void Foo::func() const
{
a = 1; // Compiler error, were func() const legal
}
Regards,
Tres.
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Wed, 4 Jan 1995 01:00:16 GMT Raw View
swillden@fcom.cc.utah.edu (Shawn Willden) writes:
[Re: const static member functions]
>Well, what is `class data'? If by class data you mean the static data
>members of the class, I don't see a problem.
>
>class Foo { static int a; static int func() const; }
>
>In Foo::func, the type of `a' would be const int.
This wouldn't give you the protection you want.
For example:
struct Foo { static int a; static int func() const; }
void bar() {
// in bar(), the type of Foo::a is `int', not `const int'.
Foo::a = 42;
}
int Foo::func() const {
// in Foo::func(), the type of Foo::a is `const int',
// but that doesn't stop us modifying it indirectly.
bar();
}
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: linus@step.polymtl.ca (Linh Dang)
Date: 4 Jan 1995 02:47:59 GMT Raw View
Fergus Henderson (fjh@munta.cs.mu.OZ.AU) wrote:
: swillden@fcom.cc.utah.edu (Shawn Willden) writes:
: [Re: const static member functions]
: >Well, what is `class data'? If by class data you mean the static data
: >members of the class, I don't see a problem.
: >
: >class Foo { static int a; static int func() const; }
: >
: >In Foo::func, the type of `a' would be const int.
: This wouldn't give you the protection you want.
: For example:
: struct Foo { static int a; static int func() const; }
: void bar() {
: // in bar(), the type of Foo::a is `int', not `const int'.
: Foo::a = 42;
: }
`class' in C++ is never const. In order to protect a class member
you have make it non-public. And in your example, you deliberately
export it to the whole world. You also use poor C++ style to
prove your point :-))) Generally, one should not use non member
function to modifie data member.
-----------
L.D.
: int Foo::func() const {
: // in Foo::func(), the type of Foo::a is `const int',
: // but that doesn't stop us modifying it indirectly.
: bar();
: }
: --
: Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: linus@step.polymtl.ca (Linh Dang)
Date: 3 Jan 1995 22:25:18 GMT Raw View
Tres Seaver (tseaver@sam.neosoft.com) wrote:
: In article <3ebsbk$r3u@news.cc.utah.edu> swillden@fcom.cc.utah.edu (Shawn Willden) writes:
: >From: swillden@fcom.cc.utah.edu (Shawn Willden)
: >Subject: Re: Post-constructors and pre-destructors
: >Date: 3 Jan 1995 16:00:52 GMT
: >Tres Seaver (tseaver@sam.neosoft.com) wrote:
: >: >If static data members define the state of the `class'
: >: >and static member funcs define its behaviour
: >: >then a const static member func would be one which
: >: >does not modifie the state of the `class'.
: >: Briefly, because tagging a member function 'const' is a signal to the compiler
: >: to use that version on const instances of the class -- how are you going to
: >: have a "const" version of the class data?
: >Well, what is `class data'? If by class data you mean the static data
: >members of the class, I don't see a problem.
: >class Foo { static int a; static int func() const; }
: >In Foo::func, the type of `a' would be const int.
: >--
: >Shawn Willden
: >swillden@icarus.weber.edu
: But the point is that there is only _one_ copy of Foo:a, so you don't
: need a separate version of Foo::func() for the non-existant 'const' copies.
: Tagging Foo::func as const thus doesn't buy any type safety, but only provides
: a promise to clients of the class not to modify (static) members. There are
: no cases in which the compiler would allow you to use the const version of
: Foo::func() when it would not also allow a non-const version.
C'mon ! it wouldn't allow you to call a non const static member function
in a const static member function of the same class.
And ppls keep complaining about non-orthogonality in C++ :-))))
---------------
L.D.
:
: Thus, static const member functions might be useful for notational purposes,
: but because they do not have the same semantics as normal const member
: functions, they could be misleading.
: It would be just as effective to publish a comment in the class declaration:
: class Foo
: {
: static int a;
: static void func(); // Does not modify static members!
: };
: except that the compiler could help catch it if you did the following:
: void Foo::func() const
: {
: a = 1; // Compiler error, were func() const legal
: }
: Regards,
: Tres.
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Sat, 24 Dec 1994 04:03:48 GMT Raw View
andys@thone.demon.co.uk (Andy Sawyer) writes:
> fjh@munta.cs.mu.OZ.AU "Fergus Henderson" writes:
>
>> andys@thone.demon.co.uk (Andy Sawyer) writes:
>>
>> > Unfortunatly, if the new operator fails [...]
>>
>> That's what you use exceptions for.
>
> Yes - it IS what I use them for, but you didn't indicate them (and didn't
>throw one in the case of new [*not the ctor, but new itself] failing...
It was actually Andrew Fitzgerald who you were originally replying to,
not me, so he was the one that didn't indicate them.
But the point is that the standard library `operator new' function will
throw an exception if it fails, it won't return a null pointer. That means
it's not necessary to explicitly check if `new' returned a null pointer.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 24 Dec 1994 09:51:14 GMT Raw View
In article <D16KBB.Iy4@world.std.com> tob@world.std.com writes:
>
>So if I understand you right, we're considering the situation where the
>base and the derived-helper-function duplicate some functionality they
>might otherwise share?
>In this case, hypothetically opening and closing
>the file in question twice. Like this?
>
>base::base()
> {
> foo( complicated, arguments, passed, much, time, consumed );
> //other code.
> helper();
> }
>
>derived::helper()
> {
> foo( complicated, arguments, passed, much, time, consumed ); //Again!
> }
>
Note this code does not work. Let me change the names
so I can understand what is happening. Suppose in a file,
there are two attributes: A and B. In all files, A is stored
at offset 0, with the same layout, but .EXE and .COM files,
while providing B, do so in a different way. One could then
sensibly write:
xfile::xfile() { getA(); }
comfile::comfile() { getB(); }
exefile::exefile() { getB(); }
where getA() is the same method for all files and getB() is
different in the two derived classes.
Now, while this is structurally correct, it requires
reopening and closing the file twice. So for example:
xfile::xopen() {..}
xfile::getA() { xopen(); ...}
comfile::getB() { xopen(); ...}
exefile::getB() { xopen(); ...}
Here there are multiple calls to "xopen" (nasty in itself)
as well as the overhead in opening the file twice.
Now, if I understand _your_ point correctly, this is the
problem to be solved. There is an obvious solution -- defer
opening the file until the derived class constructor is
run. The code then looks like:
xfile::xfile() { /* do nothing */ }
xfile::getAfromOpenFile(file_handle) { ..}
comfile::comfile() {
h= xopen(); getAfromOpenfile(h); getBfromOpenfile(); }
// smly for exefile
This is nasty, however, for several reasons. First, you need to
pass the environment to "getAfromOpenfile", introducing coupling.
Second, if you forget to write the correct code, it fails:
you have to do the right thing to get the basic information (A)
out of the file -- in EACH derived class.
And third, and probably much more important than either of these
factors, is that it is not extensible. That is, if there were
several kinds of "exe" file (DOS, NT), with some attribute C to be extracted
in different ways, then we would have to modify the base class
to take _that_ into account.
Worse, dividing files into "com" and "exe" is only one
kind of factorisation. One can imagine another kind of factorisation,
such as OS version or author, which would suggest the use of
multiple inheritance. That would make things quite complicated!
Now, your suggestion is that the base class can somehow
dispatch to the appropriate derived functions, right?
>And you're making a case that by _not_ letting the base control the
>timing, we gain more flexibility, right?
Well, I'm examining the reason why in fact the base
class _cant_, in the present language definition, either do a virtual
dispatch or schedule some "before" or "after" method. It seems
to me part of the reason is that this is not general, and more
importantly does not sit well with multiple inheritance.
On the other hand, lets consider a two phase
construction process. In the first phase, basic information
is built up. In the second phase, polymorphism works:
one often has code for which construction is not complete
until one calls a "create()" method:
Derived d(params1);
d.create(params2);
Separating these phases is useful and powerful because it permits
delaying execution of the second phase until enough information
exists to execute the second phase, for example, params2 might
contain pointers to many objects at phase1 construction:
to connect a while lattice one may make several objects
in obeying some _weak_ invariant, then link them together
so they obey a stronger invariant.
So the fact that this is controlled dynamically is more
powerful and _thus_ more error prone than if, say, the
"create()" method was dispatched after construction (phase1)
had been completed automatically. Agree?
>
>That sounds reasonable. OTOH, to my mind the base, by finishing its
>CTOR, has "promised" the derived that it's ready to go when in fact it
>isn't.
That depends on how you design your classes of course.
>
>Now, this may seem terribly pedantic, but to me overriding the
>appropriate virtual functions is a descendant's responsibility, but
>completing the base class construction is not.
Of course. But if you design, in the current language,
a base class that promises to establish an invariant that it
cannot possibly establish because it does not have enough
information, then that looks like a design error to me.
OTOH, if the base _could_ establish the invariant,
but does not for efficiency reasons, it is a different
issue. Then the question becomes
"How can the base establish the invariant if it
must (it is the only class, or the derived class
does not do the work), or give up control to the
derived class gracefully, if the derived class
can do the job more efficiently?"
I guess, intuitively, the way to do that is to
provide TWO constructors in the base class:
base::base() { /* do it myself */ }
base::base(donothing) { }
and then the derived class can say:
derived::derived() { /* assume base has done its job */ }
derived::derived() : base(donothing()) {
/* TELL base I'm going to do the job */ }
>
>Maybe it's because I personally have many times forgotten to "finish
>constructing" a base class that needed something done in the derived
>CTOR, but never forgotten to override virtual functions that needed
>changing.
Maybe you have mis-designed your base classes. If they
cannot be properly constructed without assistance from a derived
class, then perhaps the inheritance lattice needs revision.
One way of revising it which may be of assistance is
to consider separating interface classes and implementations,
that is, to use mixins.
In that paradigm, you establish the abstract lattice
quite independently of how the representation invariants
are established (because the abstract lattice is independent
of the representation!)
>Maybe it's because in a lot of cases I can just write "=0" and
>let the compiler keep us safe.
>
>So the answer to your earlier two questions would seem to be
>
>> So the question is: while there _exist_ poor strategies,
>> is there a _good_ strategy that works in C++ right now?
>
>No, and
Try mixins. I suggest they probably _will_ provide a
superior solution.
>> Another question is: is there a problem with the design
>> which is contrary to good principles of some kind?
>
>Yes.
Again, if that is so, perhaps there is a mixin
solution that works well.
>> So perhaps it is a consequence of responsibility that you can
>> in fact forget to call the appropriate functions in the CTOR,
>> or indeed call them at the wrong place.
>
>Yes, but perhaps the tradeoff is not that pure.
Agreed. However, a good engineer will understand
the "pure" solution(s) first before making tradeoffs --
which is why we are having this discussion.
(Afterwards you can hack anything you like, and you can
choose to document the hackery and why you chose to do it :-)
>OK, scrap the part of my
>thinking that had the base class dictating the timing. You've made a
>good case for that.
No. I think _you_ have made a good case that there ought
to be a _less_ general and more secure approach. Just because
allowing the "base" to control the timing seems to be overconstraining
in general does not mean there is not another way to achieve what
you want.
>
>But there's still information in the picture that a compiler can use to
>make us safer. Though I have no idea how it could fit in C++.
Mixins may help. (See later -- I'm about to be timed
out, and need to enter "phase2" construction by logging in again :-)
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 24 Dec 1994 11:51:41 GMT Raw View
In article <D16KBB.Iy4@world.std.com> tob@world.std.com writes:
[Continued-- phase 2]
>But there's still information in the picture that a compiler can use to
>make us safer. Though I have no idea how it could fit in C++.
Right. I agree. And perhaps there is _already_ a way
to do that. I mean, if the C++ system is complete, such a way
probably exists, and we need to find it -- or prove no such
way exists, so that we know C++ is not in fact complete.
>Suppose that the base class had a way of saying three things:
> "I'm not ready even if my CTOR is done"
> "data member X and member function Y
> are not to be used till I'm ready", and
> "Calling function `foo' will make me ready".
Very nice characterisation. See below. Note, at this
point, this is somewhat different than what "before" and "after"
methods might be useful for, IMHO. (You might use them to
check class invariants or provide debugging traces, or assert
and release locks in an MT environment)
>In effect, supporting construction in stages, which now can only be done
>manually.
Yes. I like this notion. What you are saying I think
is that phased construction is not supported by constructors.
>[ Since this is drifting off C++, feel free to move it to
>alt.lang.design or other more appropriate group if you prefer]
According to _my_ characterisation we are examining
if C++ is complete -- this is a language design issue of
some importance, even to Standardisation.
Analysis -- REPRESENTATION INVARIANTS AND PROTOCOLS
---------------------------------------------------
First, if you look at my exception class lattice EHMIX (posted
to comp.lang.c++) you will see I distinguish two kinds of
exception:
1) representation invariant violation
2) protocol violation
The difference is: if a representation invariant is found
to be broken, that is an error on the part of the class
designer. That is, it is a SERVER error.
On the other hand, if the class requires, say, functions
to be called in some order, and the wrong order is used,
that is a fault of the user of the class (not following
the documentation): this is a CLIENT error.
Now, in terms of your characterisation of the problem,
you are complaining that calling "Y()" (public member)
before calling "foo()" (phase 2 invariant establishment),
is a _protocol_ error rather than a representation invariant
error, that is, the fault is with the CLIENT not the SERVER.
I'd point out that protocol violations can usually be detected
at run time by setting switches indicating which stage of
invariant is established. For example
> "I'm not ready even if my CTOR is done"
Set switch "stage2done" to false.
> "data member X and member function Y
> are not to be used till I'm ready", and
Use encapsulation for X. For Y, manually
check the switch "stage2done" and throw
a protocol violation exception if false.
> "Calling function `foo' will make me ready".
In foo, set "stage2done" to true.
Now, the point of this idiom is to demonstrate that the more
general dynamic protocol requirements have corresponding
general dynamic validation techniques -- protocol is a
run time phenomena, and so in general detecting a violation
is also a run-time phenomena. This seems right -- proper coding
by the SERVER can detect violations of the rules by the CLIENT.
[NOTE: protocol violations are meaning full ONLY because
we have objects. There is no such thing as "behaviour"
in a purely functional system. This is a kind of hint
to a solution]
Having said that, we all know that run-time errors are not as
good in some cases as compile time ones. So the question is,
how can idomatic protocols be somehow bound early enough
for an error to be detected at _compile_ time using the
static type system?
What I meant by "idiomatic protocols" is that certain
categories of idiom -- some kinds of multi-stage construction
for example -- might have general type safe solutions.
Now let's look at solutions. The first observation is
that the blindingly obvious is the answer :-)
Suppose some lattice L1 is, after construction, in a state S1
such that calling functions F1 works, but one must call
E2 to establish states S2 before functions F2 can operate
correctly. (Follow?)
If one calls F2 before calling E2, we have a protocol violation
and we WANT something else -- we want such a violation
to be (statically) impossible. Agree?
The solution is, as I said, blindingly obvious.
REMOVE the functions F2 from the lattice!
Then you cannot get a protocol error, because you cant
call F2 functions.
But, how to call F2 eventually? After all, we need them.
Well, thats blindingly obvious too. Construct ANOTHER
object L2, passing L1 as an argument, or, better still,
passing the parameters required for L1 to be constructed.
L2 first constructs L1, and THEN it calls E2 -- IN ITS CONSTRUCTOR.
So, a client of L2 CANNOT access the F2 functions until AFTER
E2 has been called.
Note that L2 must call E2 manually. But, that makes sense because
L2 is supplying the F2 services and simply using L1 as a
less constrained data structure, for reusability. It can
use delegation to first construct, and then constrain, the L1
classes in its own constructor, providing the guarrantees that
F2 services will be coherent.
What, you say, if L1 contains _some_ services F1 that L2 ought
to provide? Clearly, these have to be forwarded.
What, you say, if L2 must _act_ like an L1, that is, L1 must
be a base of L2?
Again, that is easy. You must separate L1 into two classes,
an abstraction and an implementation: L1A and L1M.
Derived L1M from L1A. Derived L2 from L1A. The forwarding
functions in L2 implement L1 by delegating to L1M.
You get a "delegation" triangle:
L1A
/ \
L2--> L1M
where L2--L1M means "delegation". And a point of this technique
is it _is_ extensible. But you first must factor L2 into two classes:
L2A and L2M:
L1A
/ \
L2A L1M
/ ^
L2M ------|
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 25 Dec 1994 07:19:11 GMT Raw View
In article <3de8fu$bb3@tethys.otol.fi> tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
>
>But there is one problem: C++ doesnt allow virtual method calls in ctors.
That's not quite true. You _can_ do calls to virtual functions,
and they dispatch "correctly" up to the "type" the currently
executing CTOR is constructing.
You can't dispatch any further because the members of more
derived classes have not yet been initialised, and so any overriding
virtual in that class cannot be called because the derived class
is not yet in a state for which its method could work.
IF the method could work, you have a design error --
the method does not belong in that class. In other words,
what you want to do is a design error in itself: you want to
do something mathematically impossible, and no good language
can be held to be faulty that does not permit you to do what
cannot be implemented.
>One solution is to have a special construction method that is called every time
>the object is created. However, this method is not called automatically like
>the ctor.
So build a handle class whose constructor first constructs the
other object so it obeys a weak invariant, and then calls the
second phase polymorphic post-constructor which establishes the
full invariant. Who was it that said all problems in CS are solved
by another level of indirection?
>the complexity of the code. So I think that calling virtual methods in ctors
>should be allowed.
It _is_ allowed, and it works as you'd want it to: it does
not dispatch to the derived class because doing so would always
be an error.
>Class Database has a field pData that is a pointer to an abstract class
>AbstractCollection. It has a default initialization method InitCollection
>that can be redefined in derived classes to change the actual type of pData.
>Of course, InitCollection should be called in the ctor of Database and it
>should be called there like a normal virtual method.
"Of course", that is WRONG. It must be. If the base class
does not know the type to construct, it should not construct it
at all. Please consider separating interfaces from implementations.
In general
1) interface bases should be abstract, derivation virtual ,
and the class should have no constructors and no data
2) implementation mixins should be derived from non-virtually
in the most derived class ONLY, they should derived
virtually from the abstraction they implement
If this technique (mixins) is not dynamic enough to solve your
problem, try delegation. If that is not enough, use function
pointers. If that is not enough, try LISP or Smalltalk. :-)
>class AbstractCollection
>class List : public AbstractCollection
>class Array : public AbstractCollection
>class Database { public:
> Database() {
> // The correct InitCollection method should be called in derived classes.
> InitCollection();
> ...
>protected:
> // Derived classes should be able to have their own InitCollection methods.
> virtual void InitCollection() {pData = new List;}
> AbstractCollection* pData;
>};
>
>// Using Array instead of List.
>class ArrayDatabase : public Database {
> virtual void InitCollection() { pData = new Array;}
GAK! Defaulting is almost always a design error!
I suggest:
// Database abstraction
struct AbstractDatabase {
virtual AbstractCollection *getCollection()const=0;
};
// implementation using a pointer
class PointerDatabase : public virtual AbstractDatabase {
AbstractCollection *pData;
AbstractCollection *getCollection()const { return pData; }
protected:
void PointerDatabase(AbstractCollection *p) : pData(p) {}
};
// mixin for List kind
struct ListDatabase : PointerDatabase {
ListDataBase() : PointerDatabase(new List) {}
};
// mixin for Array kind
struct ArrayDatabase : PointerDatabase {
ArrayDataBase() : PointerDatabase(new Array) {}
};
Notice there is no "defaulting". Defaulting here is BAD (tm).
You choose the kind you want -- List or Array, you MUST make
a choice, there is no default.
In general -- when you want to delay binding of type until
construction time, use mixins. If you want to delay it further
or change the binding, use delegation. If you want to "construct"
arbitrary types and modify them, use function pointers.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa)
Date: 25 Dec 1994 14:48:33 GMT Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
: In article <3de8fu$bb3@tethys.otol.fi> tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
: >
: >But there is one problem: C++ doesnt allow virtual method calls in ctors.
: That's not quite true. You _can_ do calls to virtual functions,
: and they dispatch "correctly" up to the "type" the currently
: executing CTOR is constructing.
I meant virtual method calls that behave normally (use dynamic dispatch).
: You can't dispatch any further because the members of more
: derived classes have not yet been initialised, and so any overriding
: virtual in that class cannot be called because the derived class
: is not yet in a state for which its method could work.
: IF the method could work, you have a design error --
: the method does not belong in that class. In other words,
: what you want to do is a design error in itself: you want to
: do something mathematically impossible, and no good language
: can be held to be faulty that does not permit you to do what
: cannot be implemented.
I want that the base class could say to derived classes:
"I have a field X. You can change the way it is initialized by redefining
the method InitX. When InitX is executed your own fields are not yet
initialized, so don't use them."
I agree that this design is a little suspicious. Perhaps some kind of
multiple-stage construction would be better.
[...]
: 1) interface bases should be abstract, derivation virtual ,
: and the class should have no constructors and no data
: 2) implementation mixins should be derived from non-virtually
: in the most derived class ONLY, they should derived
: virtually from the abstraction they implement
: If this technique (mixins) is not dynamic enough to solve your
: problem, try delegation. If that is not enough, use function
: pointers. If that is not enough, try LISP or Smalltalk. :-)
What are mixins?
[example deleted]
: GAK! Defaulting is almost always a design error!
Why and where is defaulting a design error?
Isn't it one main principle in OOP that Base class's default behaviour
can be changed by inheriting from it?
: I suggest:
: // Database abstraction
: struct AbstractDatabase {
: virtual AbstractCollection *getCollection()const=0;
: };
: // implementation using a pointer
: class PointerDatabase : public virtual AbstractDatabase {
: AbstractCollection *pData;
: AbstractCollection *getCollection()const { return pData; }
: protected:
: void PointerDatabase(AbstractCollection *p) : pData(p) {}
: };
: // mixin for List kind
: struct ListDatabase : PointerDatabase {
: ListDataBase() : PointerDatabase(new List) {}
: };
: // mixin for Array kind
: struct ArrayDatabase : PointerDatabase {
: ArrayDataBase() : PointerDatabase(new Array) {}
: };
Somehow my own example was easier to understand...
Tommi H yn l nmaa
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Wed, 28 Dec 1994 01:07:50 GMT Raw View
In article <3dk0o1$9o1@tethys.otol.fi> tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
>John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
>: In article <3de8fu$bb3@tethys.otol.fi> tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
>: >
>: >But there is one problem: C++ doesnt allow virtual method calls in ctors.
>
>: That's not quite true. You _can_ do calls to virtual functions,
>: and they dispatch "correctly" up to the "type" the currently
>: executing CTOR is constructing.
>
>I meant virtual method calls that behave normally (use dynamic dispatch).
They DO use dynamic dispatch. But the run time type of
the object is not yet fully established -- the dispatch correctly
goes to the existant object and no further.
>
>: 1) interface bases should be abstract, derivation virtual ,
>: and the class should have no constructors and no data
>
>: 2) implementation mixins should be derived from non-virtually
>: in the most derived class ONLY, they should derived
>: virtually from the abstraction they implement
>
>: If this technique (mixins) is not dynamic enough to solve your
>: problem, try delegation. If that is not enough, use function
>: pointers. If that is not enough, try LISP or Smalltalk. :-)
>
>What are mixins?
An abstract lattice with implementation classes hanging off
the nodes.
>: GAK! Defaulting is almost always a design error!
>
>Why and where is defaulting a design error?
Good question.
>Isn't it one main principle in OOP that Base class's default behaviour
>can be changed by inheriting from it?
NO! Absolutely not, never. Exactly the opposite is true.
You should never _ever_ change the behaviour of a base function.
If a base function T B::f() returns values for the states S
of the object, then for an overriding function T D::f(),
OO REQUIRES that for each state s of S
s.B::f() == s.D::f()
Otherwise your program's behaviour depends in the case
B* b;
b->f()
on whether the actual type of the object denoted by *b is a B or a D.
It must not, or polymorphism will not work.
A default is useful when there are a set of choices,
and one of them is common enough to not want to have to bother
specifying it all the time.
For example, consider a graphics class
struct drawable {
virtual void draw(Point)=0;
};
You can then define:
struct point : drawable { .. }
struct circle: drawable { .. }
struct rectangle : drawable { .. }
Notice there is NO default draw method. The draw method of the
base drawable is pure virtual, that is, it can draw anything.
In the derived class you can draw a subset of anything. :-)
This is exactly the opposite of a default. Instead of allowing
you to use the base to say, draw a point, _must_ instead
override the function, because it is pure. And then,
the classic, and contentious example:
struct square : rectangle { .. }
makes sense: the rectangle class can already draw squares,
the point of the square class is:
a) to do it more efficiently, including using
less storage in the implementation
Notice that for this to work, "rectangle" itself should be ABSTRACT.
In general, the classes from which you derive should be abstract.
b) to guarrantee there is a way of drawing squares
without having to give two equal parameters to the
rectangle class
>: I suggest:
[example deleted]
>Somehow my own example was easier to understand...
Obviously not, since it did not work, and the code
above will. Learning relativity is hard, it require unlearning
Newton. Learning OOP is hard, it requires a paradigm shift.
Mixins are easy to write -- it is true they are not
so easy to _read_, mainly because there are more classes to
deal with. Typically you need to define 2-3 times more classes.
Luckily, most errors are caught by static type checking.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: tob@world.std.com (Tom O Breton)
Date: Wed, 21 Dec 1994 21:42:47 GMT Raw View
[ Newsgroups trimmed, comp.lang.c++ removed ]
> So at the moment, "compute_memory_required()" can be
> DEFINED in the base class but it must be CALLED in the body
> of each derived class constructor. And you'd like the base class
> to enforce this with a "dispatch me before derived class constructor
> body executes" command. Yes?
Right, exactly.
> So the question is: while there _exist_ poor strategies,
> is there a _good_ strategy that works in C++ right now?
>
> Another question is: is there a problem with the design
> which is contrary to good principles of some kind?
OK, yes, those seem to be the relevant questions.
> Basically, the requirement is that some function be executed
> AFTER members and bases of the MOST DERIVED CLASS are initialised.
> It is only at this point that
>
> a) the correct polymorphic dispatches are performed
> b) the data required to evaluate those functions
> correctly _can_ exist.
>
> In (b) the word "can" is leading. It may be in some cases
> that extra operations must be performed in the body
> of the constructor _before_ the most derived class is
> in a state for which the function can be executed.
Right. That, in a nutshell, is why I've been yammering about different
user-defined stages of construction.
> The existing language structure is most general in that you
> not only _can_ but _must_ call the base algorithm _when_
> and _not until_ the appropriate representation invariants
> of the derived class are established.
[...]
> For example: suppose the base class accepts the file name,
> but one must obtain size information by reading the file:
> the layout of exe and com files is different, and
> the size computation, if dispatched EARLIER than the body
> of the derived constructor would require you to
> READ THE FILE in a member constructor, whereas the natural
So if I understand you right, we're considering the situation where the
base and the derived-helper-function duplicate some functionality they
might otherwise share? In this case, hypothetically opening and closing
the file in question twice. Like this?
base::base()
{
foo( complicated, arguments, passed, much, time, consumed );
//other code.
helper();
}
derived::helper()
{
foo( complicated, arguments, passed, much, time, consumed ); //Again!
}
And you're making a case that by _not_ letting the base control the
timing, we gain more flexibility, right?
That sounds reasonable. OTOH, to my mind the base, by finishing its
CTOR, has "promised" the derived that it's ready to go when in fact it
isn't.
Now, this may seem terribly pedantic, but to me overriding the
appropriate virtual functions is a descendant's responsibility, but
completing the base class construction is not.
Maybe it's because I personally have many times forgotten to "finish
constructing" a base class that needed something done in the derived
CTOR, but never forgotten to override virtual functions that needed
changing. Maybe it's because in a lot of cases I can just write "=0" and
let the compiler keep us safe.
So the answer to your earlier two questions would seem to be
> So the question is: while there _exist_ poor strategies,
> is there a _good_ strategy that works in C++ right now?
No, and
> Another question is: is there a problem with the design
> which is contrary to good principles of some kind?
Yes.
> So perhaps it is a consequence of responsibility that you can
> in fact forget to call the appropriate functions in the CTOR,
> or indeed call them at the wrong place.
Yes, but perhaps the tradeoff is not that pure. OK, scrap the part of my
thinking that had the base class dictating the timing. You've made a
good case for that.
But there's still information in the picture that a compiler can use to
make us safer. Though I have no idea how it could fit in C++.
Suppose that the base class had a way of saying three things:
"I'm not ready even if my CTOR is done"
"data member X and member function Y are not to be used till I'm ready", and
"Calling function `foo' will make me ready".
In effect, supporting construction in stages, which now can only be done
manually.
[ Since this is drifting off C++, feel free to move it to
alt.lang.design or other more appropriate group if you prefer]
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 21 Dec 1994 22:47:19 GMT Raw View
Well, I've always thought that Ada had much more sensible
initialization schemes in general. Doesn't surprise me that it
handles this type of thing well. A question though. I've read some
papers on Ada95 (do we really believe the '95?), and they have all
been trying to explain OO to Ada folks and justify why it could still
be Ada (read structured). Can you or anyone out there point me to a
good paper that frames the new Ada features in an OOP context (maybe
compares it to other OOP languages), rather than showing how the new
features are really just like old Ada. BTW, I do know Ada83, and
can't figure out why the OO extensions can't be described in about 2-3
clear pages. Maybe extended study of your example will help.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: andys@thone.demon.co.uk (Andy Sawyer)
Date: Fri, 23 Dec 1994 01:51:43 +0000 Raw View
In article <9435514.4794@mulga.cs.mu.OZ.AU>
fjh@munta.cs.mu.OZ.AU "Fergus Henderson" writes:
>
> andys@thone.demon.co.uk (Andy Sawyer) writes:
>
> > Unfortunatly, if the new operator fails [...]
>
> That's what you use exceptions for.
>
Yes - it IS what I use them for, but you didn't indicate them (and didn't
throw one in the case of new [*not the ctor, but new itself] failing...
Regards,
Andy
--
=============================================================================
| Andy Sawyer Internet (Personal) : andys@thone.demon.co.uk |
| Compu$erve (Business) : 100432,1713 |
=============================================================================
|The opinions expressed above are my own, but you are granted the right to |
|use and freely distribute them. I accept no responsibility for any injury, |
|harm or damage arising from their use. -- The Management. |
=============================================================================
Author: tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa)
Date: 23 Dec 1994 10:23:58 GMT Raw View
Sometimes you want to have "polymorphic fields" in classes. I.e., fields
whose type can be changed in derived classes. These are implemented as pointers
or references in C++. Because the type of the polymorphic field may be
different in derived classes, the method that initializes it has to be virtual.
But there is one problem: C++ doesnt allow virtual method calls in ctors.
One solution is to have a special construction method that is called every time
the object is created. However, this method is not called automatically like
the ctor. So there is a possibility that the object remains in an invalid state,
where one of is members is not constructed at all. This is almost the same
argument that is used against allowing the virtual method calls in ctors.
Forbidding virtual method calls in ctors may give more security somewhere, but
on the other hand it may also force you to write incomplete ctors and increase
the complexity of the code. So I think that calling virtual methods in ctors
should be allowed. Even if this was done, you could of course write calls like
OwnClass:Method(...) in ctors to have the present behaviour. Compilers could
also give a warning when a virtual method is called in a ctor.
An example about virtual method calls in ctors:
Class Database has a field pData that is a pointer to an abstract class
AbstractCollection. It has a default initialization method InitCollection
that can be redefined in derived classes to change the actual type of pData.
Of course, InitCollection should be called in the ctor of Database and it
should be called there like a normal virtual method.
I would be also interested in other solutions (that work in present C++).
class AbstractCollection
{
..
};
class List : public AbstractCollection
{
..
};
class Array : public AbstractCollection
{
..
};
class Database
{
public:
Database()
{
// The correct InitCollection method should be called in derived classes.
InitCollection();
...
}
...
protected:
// Derived classes should be able to have their own InitCollection methods.
virtual void InitCollection()
{
pData = new List;
}
AbstractCollection* pData;
};
// Using Array instead of List.
class ArrayDatabase : public Database
{
protected:
virtual void InitCollection()
{
pData = new Array;
}
..
};
Tommi H yn l nmaa (7-bit ASCII: H|yn{l{nmaa)
Author: beckwb@ois.com (R. William Beckwith)
Date: 19 Dec 1994 21:16:25 -0500 Raw View
William A. Law (bill_law@taligent.com) wrote:
: All I would want is to be able to reuse the inherited constructor but have
: it do things in a manner appropriate for my derived class. A simplistic
: example is:
: struct base {
: base ( ) { cout << "Constructing a " << className() << endl; }
: virtual char *className() { return "base"; }
: };
: struct derived : base {
: virtual char *className() { return "derived" };
: };
: derived d; // I want it to print "Constructing a derived"!!!!!
: My solution to this problem is simple. Make className() a static member
: function. This is more "correct" anyway (since the body of the function
: never refers to "this" it obviously doesn't *need* a "this" pointer).
: By making the function static, we remove the obstacle to calling the
: virtual function in the base class constructor: Since there is no
: (implied) derived pointer passed to className(), the fact that we have
: only a base pointer is moot.
: The only remaining problem is that static functions can't be virtual.
: Whenever the idea of permitting this is brought up, it is usually shot
: down with the argument: "Static virtuals aren't necessary. Since you
: would need an object in order to determine which implementation to call,
: just make the function a member function." I think the potential usage of
: static virtuals in constructors exposes one important case where that
: argument is deficient.
I was curious how the new Ada 95 would handle the above. Here's the
solution:
Compile and run:
gamma{beckwb}548: gcc -c test.adb hello.adb
gamma{beckwb}549: gnatbl -o hello hello.ali
gamma{beckwb}550: hello
Constructing a derived
Source:
--- test.ads:
with Ada.Finalization;
package Test is
type Base is new Ada.Finalization.Controlled with null record;
function Class_Name (Self : in Base) return string;
procedure Initialize (Self : in out Base);
type Derived is new Base with null record;
function Class_Name (Self : in Derived) return string;
D : Derived;
end Test;
--- test.adb:
with Text_IO;
package body Test is
function Class_Name (Self : in Base) return string is
begin
return "base";
end Class_Name;
function Show_Class_Name (Self : in Base'CLASS) return string is
begin
return Class_Name(Self);
end Show_Class_Name;
procedure Initialize (Self : in out Base) is
begin
Text_IO.Put_Line("Constructing a " & Show_Class_Name(Self));
end Initialize;
function Class_Name (Self : in Derived) return string is
begin
return "derived";
end Class_Name;
end Test;
--- hello.adb:
with Test;
procedure Hello is
begin
null;
end Hello;
Author: tob@world.std.com (Tom O Breton)
Date: Mon, 19 Dec 1994 20:22:10 GMT Raw View
bill_law@taligent.com (William A. Law) writes:
> My solution to this problem is simple. Make className() a static member
> function. This is more "correct" anyway (since the body of the function
Very briefly, a static member function does not automatically know about
any particular instance. Therefore it can't exhibit virtual behavior,
because it has no referent _to be virtual in regard to_.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Tue, 20 Dec 1994 23:38:50 GMT Raw View
In article <D0vuC0.87E@world.std.com> tob@world.std.com writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>> No, my point was more like any call of a virtual
>> during construction works properly anyhow: there's no
>> need for a "do before" or "do after" method for constructors.
>
>> Example?
>
>OK. In general, the idea is that you want a thing to be fully
>constructed but some details must be handled differently in derived
>classes, and those details are embedded in some logic.
>
>I wrote a driver-loader class for DOS. There are 2 types of executables
>in DOS, memory image files (*.COM) and files whose prefix is interpreted
>to fix up memory addresses et. al. (*.EXE).
>
>I have an abstract base class, and (if the features were there to do
>this the right way) two derived classes, one of which handles each type.
>The correct driver should be completely loaded by the time the CTOR is
>done.
Yep, sounds sound <grin>.
>
>Furthermore, I don't want to leave this up to the derived CTORs, because
>certain things must always be done, such as finding out how much memory
>is required (which is figured out in different ways for each) and
>allocating that amount, and some alignment stuff that is neccessary and
>fails in a hard-to-debug way, so I'd like to do that logic _only once_
>in the base.
Let me see if I understand. There is some algorithm,
for example:
struct Base {
int ram;
int compute_file_size()const { ..}
virtual int compute_extra_needed()const=0;
// common algorithm
void compute_memory_required() {
ram = compute_file_size() + compute_extra_needed();
}
...
};
which is the same for all derived classes and so belongs in the base,
but the functions called in its implementation depend on the
derived classes -- for example "compute_extra_needed" has to
be polymorphic.
>
>Ideally, I could communicate with the compiler to tell it that certain
>virtual functions don't/mustn't use anything outside the base class, and
>then be able to call them "properly" }:) in the base CTOR.
So at the moment, "compute_memory_required()" can be
DEFINED in the base class but it must be CALLED in the body
of each derived class constructor. And you'd like the base class
to enforce this with a "dispatch me before derived class constructor
body executes" command. Yes?
>
>Other strategies are repetitive, overexposed and clumsy, or don't allow
>me to seperate the different behavior into seperate classes (which is my
>workaround).
I agree there exist other strategies with these undesirable
properties.
>Since this is a simple example with only two "branches", the workaround
>isn't onerous, but the general principle stands.
Agreed: one can imagine not only 20 derived classes,
but classes derived from them. And one can imagine MULTIPLE
base classes and MULTIPLE such algorithms which can be
defined in bases but which must be executed in derived classes
to ensure polymorphism works.
So the question is: while there _exist_ poor strategies,
is there a _good_ strategy that works in C++ right now?
Another question is: is there a problem with the design
which is contrary to good principles of some kind?
If either of these questions can be answered YES, then
one must consider not only that C++ is adequate, but that an
extension of the kind indicated would be suspect, if the answer
to both is NO then it would seem C++ is lacking a useful facility.
Analysis.
---------
The first thing that makes me suspicious is the requirement.
Basically, the requirement is that some function be executed
AFTER members and bases of the MOST DERIVED CLASS are initialised.
It is only at this point that
a) the correct polymorphic dispatches are performed
b) the data required to evaluate those functions
correctly _can_ exist.
In (b) the word "can" is leading. It may be in some cases
that extra operations must be performed in the body
of the constructor _before_ the most derived class is
in a state for which the function can be executed.
For example if one of the members was a pointer, and it had
to be assigned in the body of the constructor, and the
computation "compute_extra_needed()" required that pointer
to be initialised.
The existing language structure is most general in that you
not only _can_ but _must_ call the base algorithm _when_
and _not until_ the appropriate representation invariants
of the derived class are established.
Sometimes that may well be immediately at the { of the ctor
of the most derived class, if not before. ANd sometimes after.
And a language extension for a "before" call would then
_prevent_ design of derived classes whose representation
required the body to do some work to establish the appropriate
invariants -- and would _discourage_ clean designs where it
was possible to establish the invariant earlier but very clumbsy
to do so.
For example: suppose the base class accepts the file name,
but one must obtain size information by reading the file:
the layout of exe and com files is different, and
the size computation, if dispatched EARLIER than the body
of the derived constructor would require you to
READ THE FILE in a member constructor, whereas the natural
way to do this may be
Derived::Derived() {
FILE *f = fopen(filename, "rb");
... get info
fclose(f);
compute_memory_required();
// do something knowing how much ram is required
}
Being _forced_ by the base class to do the file reading
_prior_ to the { seems to require ugly hackery.
So perhaps there is a good reason why "before" and "after"
methods were actually _removed_ from the C++ language.
(Yes, it used to have them). And that is that they
permit the base class to constrain the design of the representation
details of the derived class -- a responsibility that belongs
exclusively to the derived class.
So perhaps it is a consequence of responsibility that you can
in fact forget to call the appropriate functions in the CTOR,
or indeed call them at the wrong place.
I quit physics, computer science, and eventually university
altogther because of this kind of constraint: lecturers
who would first set a problem to be solved and then tell you
how it HAD to be solved.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: fjh@munta.cs.mu.OZ.AU (Fergus Henderson)
Date: Wed, 21 Dec 1994 03:23:34 GMT Raw View
andys@thone.demon.co.uk (Andy Sawyer) writes:
> Unfortunatly, if the new operator fails [...]
That's what you use exceptions for.
--
Fergus Henderson - fjh@munta.cs.mu.oz.au
Author: tob@world.std.com (Tom O Breton)
Date: Sat, 17 Dec 1994 04:14:28 GMT Raw View
olaf@cwi.nl (Olaf Weber) writes:
> It seems to me that the problem is that you need information (in this
> case an algorithm) in the base class, where that information is only
> available in the derived classes.
>
> This can be done by passing a function to the base class constructor.
Yes, thank you, that's an approach I considered.
Anyways, this thread is not really about my application, for which I
have a serviceable workaround, but about virtual functionality WRT base
CTORs.
There are a number of workarounds: physically repeat code, pass function
pointers, pass "fake virtual tables" (structs of function pointers),
combine classes, or call init() by hand, but none of them are really
"pure" or totally safe.
Basically, the reasons polymorphism is helpful after the class is
constructed can also apply while it is being constructed.
> If you considered this method, and rejected it, I'd be very interested
> in learning why.
In the example at hand, the reason I didn't use that is because there's
only two branches and there will never be more, so I just shoveled the
functionality for both into the base.
I reject passing a pointer as a general solution because it's clumsy and
overexposed: every (non-abstract) base and intermediate class must have
2 CTORs whereever there was 1 before:
One to forward the function-pointer(s) from descendant to root,
which all descendants should use, and
One to insert its own function-pointer(s), which no descendant
should use but which must be public.
Therefore there's always a possibility that a descendant will use the
wrong base CTOR, quietly making a disaster. Also a descendant may forget
to even define the neccessary second CTOR(s).
Note that in principle there could be hundreds of function-pointers that
need passing. Presumably they'd be passed as a single pointer to a
struct containing many pointers, in essence _recreating_ the virtuality
mechanism, just less safely and with a lot more programmer effort and
slightly more runtime cost.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: andrewfg@aisb.ed.ac.uk (Andrew Fitzgibbon)
Date: Sat, 17 Dec 1994 12:59:38 GMT Raw View
John Max Skaller (maxtal@physics.su.OZ.AU) wrote:
< In article <DOUG.94Dec11013112@monet.ads.com> doug@monet.ads.com (Doug Morgan) writes:
< >In article <D0Mqn8.LxA@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
< Hm. But this is not quite right. Consider
< you want to do
< before(), body(), after()
< in order, where one or all three are polymorphic. Well, thats easy:
< void X::bba() { before(); body(); after(); }
< Now the fact that you may have several functions to wrap like
< this is perhaps a hassle, but a minor one: it can be done
< by the class designer and happens automatically in the derived
< class.
< The only place you _cant_ use this technique is in constructors
< and destructors. (And assignments) Why? Is that a bug in C++ or a logical
< inevitability?
You can however substititute
class X {
virtual void f(int);
X(int i) { f(i); } // F not called thru vtable
};
X * xptr = new X(1);
with
class X {
virtual void f(int);
X() {}
X * construct(int i) { f(i); return this; }
};
X * xptr = (new X)->construct(1);
or (yuck)
#define NEW(T) (new T)->construct
X * xptr = NEW(X)(1);
Thereby ensuring that the vtbl is built before the 'constructor' is
called.
A.
--
Andrew Fitzgibbon (Research Associate), andrewfg@ed.ac.uk
Artificial Intelligence, Edinburgh University. +44 031 650 4504
http://www.dai.ed.ac.uk/staff/personal_pages/andrewfg
"If it ain't broke, don't fix it" - traditional (c1950)
"A stitch in time saves nine." - traditional (c1590)
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 16 Dec 1994 22:43:31 GMT Raw View
> Other strategies are repetitive, overexposed and clumsy, or don't
> allow me to seperate the different behavior into seperate classes
> (which is my workaround).
It seems to me that the problem is that you need information (in this
case an algorithm) in the base class, where that information is only
available in the derived classes.
This can be done by passing a function to the base class constructor.
class B {
protected:
B(char const * (*helper)())
{ cout << (*helper)() << endl; }
};
struct D: public B {
D() : B(&id) { }
private:
static char const * id() { return "D"; }
};
There is one unpleasant complication here: if the function in the
derived class takes a pointer to the base as a parameter, it can only
access the public members of B.
This is (arguably) a problem with the "protected" concept of C++:
weaker protection would make better encapsulation easier in this case.
One workaround is to make D a friend of B, but it is
undesirable that B should know all of its derived classes.
A second workaround is to pass as arguments the members of B
that the helper should have access to. This is somewhat clumsy, but
it is the method I'd prefer.
If you considered this method, and rejected it, I'd be very interested
in learning why.
I'll be off during the next two weeks, so if you want me to see any
followups, mail them to me.
-- Olaf Weber (olaf@cwi.nl)
Well, aside from the fact that you're putting the burden on the user
to initialize you class properly, and that it's an ugly procedural
hack, that goes completely against the grain of OO, I guess it's OK.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: andys@thone.demon.co.uk (Andy Sawyer)
Date: Sun, 18 Dec 1994 14:56:54 +0000 Raw View
In article <D0yHFE.H3M@aisb.ed.ac.uk>
andrewfg@ed.ac.uk "Andrew Fitzgibbon" writes:
[snip]
>
> class X {
> virtual void f(int);
> X() {}
> X * construct(int i) { f(i); return this; }
> };
> X * xptr = (new X)->construct(1);
> or (yuck)
> #define NEW(T) (new T)->construct
>
> X * xptr = NEW(X)(1);
>
> Thereby ensuring that the vtbl is built before the 'constructor' is
> called.
>
Unfortunatly, if the new operator fails (i.e. returns a null pointer - and
this can happen - you are now somewhere in hyperspace....or wading through
a core dump....
More precisely, if new fails to allocate your 'X', then the vtbl will *NOT*
be built, so dereferencing the 'construct' member could get you anything (but
most likely not the X::construct member!)
So, you then end up with:
X* pX = new X;
if( pX )
{
pX->Construct(1);
}
else
{
// Fail gracefully!
}
A 'Post-Constructor' mechanism should, if properly defined and implemented,
avoid this problem.
Out of interest, I have seen commercial class libraries which RELIED on the
virtual mechanism working during destructors...I won't name any names, since
I guess it's out of circulation by now (it was for version 1 of the language
- remeber the anonymous base class constructor? :-)
the base class actually did something like:
class Base {
protected:
virtual void Store( void *memblk ) = 0;
Base()
public:
virtual ~Base();
};
Base::~Base()
{
void *pMem = new char[512];
Store( pMem );
// process the memory
delete [] pMem;
}
Now, it is obvious that in Base::~Base, the call to store is to a pure
virtual function - not good news! (Again, the class library vendor's compiler
supported this behaviour - the compiler has subsequently been sold to
another vendor, whose staff seemed very worried when I queried them about
this at a recent exhibition....)
Regards
Andy
--
=============================================================================
| Andy Sawyer Internet (Personal) : andys@thone.demon.co.uk |
| Compu$erve (Business) : 100432,1713 |
=============================================================================
|The opinions expressed above are my own, but you are granted the right to |
|use and freely distribute them. I accept no responsibility for any injury, |
|harm or damage arising from their use. -- The Management. |
=============================================================================
Author: pete@borland.com (Pete Becker)
Date: Thu, 15 Dec 1994 18:48:18 GMT Raw View
In article <D0uAnK.FHB@beaver.cs.washington.edu>,
drogers@cs.washington.edu says...
>
>In article <u34wkW1occWS075yn@virtuoso.com> mikeryan@virtuoso.com
writes:
>...
>>It's a common desire to be able to call a virtual function at the
>>time an object is constructed (or destroyed). As a matter of fact,
>>probably the most common mistake made by C++ programmers (generally
>>at just about the time they think they're starting to understand
>>the language) is to call a virtual function in their constructor and
>>get surprised when it doesn't call the derived member function. It
>>makes perfect sense once it's explained that the derived object
>>doesn't exist yet...
>
>Huh? It doesn't make any sense to me. Exactly what is it about the
>object is not constructed once we have reached the body of the CTOR?
>All of the bases and members have been constructed? The only thing
>that hasn't happened is the stuff that we would be doing in the CTOR.
>So does it not make sense that the writer of the CTOR could be
>responsible enough to figure out if a particular virtual function
>could be safely called? It seems a lot better way to handle
>the "problem" than the current, "call the wrong function" solution.
>
>The vtbl should be in place and active before the body of the CTOR is
>activated, and should remain in place until after the body of the DTOR
>has terminated.
>
There's nothing wrong with calling a virtual function from a
constructor. The limitation is that it will not call an overriding
function in some derived class. As the original posting said, this makes
good sense, because the derived class has not been constructed at the
time that the body of the base class's constructor is being executed.
Here's a silly example:
class Base
{
virtual void f() {};
public:
Base() { f(); }
};
class Derived : public Base
{
char *text;
void f() { cout << text << endl; }
public:
Derived() { test = "testing"; }
};
If the call to f() from Base() results in the execution of Derived::f()
there is a good chance that the progam will crash. In any event it will
display nonsense, because text has not been initialized.
-- Pete
Author: tob@world.std.com (Tom O Breton)
Date: Fri, 16 Dec 1994 02:45:36 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
> No, my point was more like any call of a virtual
> during construction works properly anyhow: there's no
> need for a "do before" or "do after" method for constructors.
"properly", eh? I think we may mean different things by that. I consider
that once a function is defined virtual, it's "proper" behavior when
called is to invoke the member of the family of functions that was
defined in the derived class, or failing that, the most recent
definition available to the derived class. To me that's what "virtual"
means, and should not require a special understanding for CTORs, which
is why IMO they should require explicit scope-access syntax if called
where they cannot be virtual.
> Example?
OK. In general, the idea is that you want a thing to be fully
constructed but some details must be handled differently in derived
classes, and those details are embedded in some logic.
I've got plenty of examples in my code, so I'll describe one. There is a
bit of half-truth in the following, since I have a somewhat clumsy
workaround that I won't confuse the issue with just yet.
I wrote a driver-loader class for DOS. There are 2 types of executables
in DOS, memory image files (*.COM) and files whose prefix is interpreted
to fix up memory addresses et. al. (*.EXE).
I have an abstract base class, and (if the features were there to do
this the right way) two derived classes, one of which handles each type.
The correct driver should be completely loaded by the time the CTOR is
done.
Furthermore, I don't want to leave this up to the derived CTORs, because
certain things must always be done, such as finding out how much memory
is required (which is figured out in different ways for each) and
allocating that amount, and some alignment stuff that is neccessary and
fails in a hard-to-debug way, so I'd like to do that logic _only once_
in the base.
Ideally, I could communicate with the compiler to tell it that certain
virtual functions don't/mustn't use anything outside the base class, and
then be able to call them "properly" }:) in the base CTOR.
Other strategies are repetitive, overexposed and clumsy, or don't allow
me to seperate the different behavior into seperate classes (which is my
workaround).
Since this is a simple example with only two "branches", the workaround
isn't onerous, but the general principle stands.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: olaf@cwi.nl (Olaf Weber)
Date: Fri, 16 Dec 1994 08:14:55 GMT Raw View
In article <D0vuC0.87E@world.std.com>, tob@world.std.com (Tom O Breton) writes:
[ On a request (by John MAX Skaller) for an example of why virtual
functions should behave in constructors/destructors as in other
member functions. ]
> OK. In general, the idea is that you want a thing to be fully
> constructed but some details must be handled differently in derived
> classes, and those details are embedded in some logic.
[ ... ]
> Ideally, I could communicate with the compiler to tell it that
> certain virtual functions don't/mustn't use anything outside the
> base class, and then be able to call them "properly" }:) in the base
> CTOR.
> Other strategies are repetitive, overexposed and clumsy, or don't
> allow me to seperate the different behavior into seperate classes
> (which is my workaround).
It seems to me that the problem is that you need information (in this
case an algorithm) in the base class, where that information is only
available in the derived classes.
This can be done by passing a function to the base class constructor.
class B {
protected:
B(char const * (*helper)())
{ cout << (*helper)() << endl; }
};
struct D: public B {
D() : B(&id) { }
private:
static char const * id() { return "D"; }
};
There is one unpleasant complication here: if the function in the
derived class takes a pointer to the base as a parameter, it can only
access the public members of B.
This is (arguably) a problem with the "protected" concept of C++:
weaker protection would make better encapsulation easier in this case.
One workaround is to make D a friend of B, but it is
undesirable that B should know all of its derived classes.
A second workaround is to pass as arguments the members of B
that the helper should have access to. This is somewhat clumsy, but
it is the method I'd prefer.
If you considered this method, and rejected it, I'd be very interested
in learning why.
I'll be off during the next two weeks, so if you want me to see any
followups, mail them to me.
-- Olaf Weber (olaf@cwi.nl)
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 13 Dec 1994 21:03:01 GMT Raw View
Hm. But this is not quite right. Consider
you want to do
before(), body(), after()
in order, where one or all three are polymorphic. Well, thats easy:
void X::bba() { before(); body(); after(); }
Now the fact that you may have several functions to wrap like
this is perhaps a hassle, but a minor one: it can be done
by the class designer and happens automatically in the derived
class.
The only place you _cant_ use this technique is in constructors
and destructors. (And assignments) Why? Is that a bug in C++ or a logical
inevitability?
It's standard stuff in CLOS. I think it's a language failing.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 13 Dec 1994 21:07:06 GMT Raw View
| Hm. But this is not quite right. Consider
|you want to do
|
| before(), body(), after()
|
|in order, where one or all three are polymorphic. Well, thats easy:
|
| void X::bba() { before(); body(); after(); }
|
|Now the fact that you may have several functions to wrap like
|this is perhaps a hassle, but a minor one: it can be done
|by the class designer and happens automatically in the derived
|class.
|
|The only place you _cant_ use this technique is in constructors
|and destructors. (And assignments) Why? Is that a bug in C++ or a logical
|inevitability?
It's just a feature of C++. You can't specify the actions *outside*
the code body. When C++ follows its rules for accessing code bodies
in constructors, destructors, and assignments, there is no place to
put the call to the action so it always get executed at the right
place. You just have to copy it over and over where it is needed.
C++ could be (but certainly won't be, and I think shouldn't be)
extended to handle these cases the way you would want.
While I'm not getting my hopes up. I'd like to know why you would
oppose such an extension.
-Malcolm
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: drogers@cs.washington.edu (David Rogers)
Date: Thu, 15 Dec 1994 06:42:39 GMT Raw View
In article <u34wkW1occWS075yn@virtuoso.com> mikeryan@virtuoso.com writes:
...
>It's a common desire to be able to call a virtual function at the
>time an object is constructed (or destroyed). As a matter of fact,
>probably the most common mistake made by C++ programmers (generally
>at just about the time they think they're starting to understand
>the language) is to call a virtual function in their constructor and
>get surprised when it doesn't call the derived member function. It
>makes perfect sense once it's explained that the derived object
>doesn't exist yet...
Huh? It doesn't make any sense to me. Exactly what is it about the
object is not constructed once we have reached the body of the CTOR?
All of the bases and members have been constructed? The only thing
that hasn't happened is the stuff that we would be doing in the CTOR.
So does it not make sense that the writer of the CTOR could be
responsible enough to figure out if a particular virtual function
could be safely called? It seems a lot better way to handle
the "problem" than the current, "call the wrong function" solution.
The vtbl should be in place and active before the body of the CTOR is
activated, and should remain in place until after the body of the DTOR
has terminated.
Author: pp000238@pop3.interramp.com
Date: Wed, 14 Dec 94 23:56:13 PDT Raw View
In article <MALCOLM.94Dec13160706@xenon.mlb.sticomet.com>,
<malcolm@xenon.mlb.sticomet.com> writes:
> Path: interramp.com!psinntp!mlb.sticomet.com!viper!malcolm
> From: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
> Newsgroups: comp.std.c++,comp.lang.c++
> Subject: Re: Post-constructors and pre-destructors
> Date: 13 Dec 1994 21:07:06 GMT
> Organization: Software Technology, Inc., Melbourne, FL
> Lines: 45
> Message-ID: <MALCOLM.94Dec13160706@xenon.mlb.sticomet.com>
> References: <D0KGr7.9sD@world.std.com> <D0Mqn8.LxA@ucc.su.OZ.AU>
> <DOUG.94Dec11013112@monet.ads.com> <D0pAI6.Hrt@ucc.su.OZ.AU>
> <DOUG.94Dec12105817@monet.ads.com>
> NNTP-Posting-Host: xenon.mlb.sticomet.com
> In-reply-to: doug@monet.ads.com's message of 12 Dec 94 10:58:17
> Xref: interramp.com comp.std.c++:3531 comp.lang.c++:25352
>
> | Hm. But this is not quite right. Consider
> |you want to do
> |
> | before(), body(), after()
> |
> |in order, where one or all three are polymorphic. Well, thats easy:
> |
> | void X::bba() { before(); body(); after(); }
> |
> |Now the fact that you may have several functions to wrap like
> |this is perhaps a hassle, but a minor one: it can be done
> |by the class designer and happens automatically in the derived
> |class.
> |
> |The only place you _cant_ use this technique is in constructors
> |and destructors. (And assignments) Why? Is that a bug in C++ or a logical
> |inevitability?
>
> It's just a feature of C++. You can't specify the actions *outside*
> the code body. When C++ follows its rules for accessing code bodies
> in constructors, destructors, and assignments, there is no place to
> put the call to the action so it always get executed at the right
> place. You just have to copy it over and over where it is needed.
> C++ could be (but certainly won't be, and I think shouldn't be)
> extended to handle these cases the way you would want.
>
> While I'm not getting my hopes up. I'd like to know why you would
> oppose such an extension.
>
> -Malcolm
>
> A constructor by definition initializes an object. You wouldn't want an
object to execute its body() if the constructor didn't initialize the
object correctly.
Its best to keep the constructor simple - use it for initialization- that is
what its for.
Another observation:
before(), body(), after() sounds very procedural or are you trying to implement
some twist on the notion of preconditions and postconditions (ala Eiffel?)
Mick
> --
> ____________________________________________________________________________
> ___________
> Malcolm McRoberts
> STI
> 1225 Evans Rd.
> Melboune, Fl. 32904
> Email: malcolm@sticomet.com
> Ph: (407) 723-3999
> Fax: (407) 676-4510
> ____________________________________________________________________________
> ___________
>
>
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Thu, 15 Dec 1994 15:25:53 GMT Raw View
In article <D0oKwB.64z@world.std.com> tob@world.std.com writes:
>maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>> I don't think there is any problem with constructors.
>
>Let me see if I understand your point:
>
> In theory everything you need to do in constructing an object
> can be done at some stage in the CTOR chain without using
> polymorphism.
>
>Is that it?
No, my point was more like any call of a virtual
during construction works properly anyhow: there's no
need for a "do before" or "do after" method for constructors.
>
>Ah, I see your point now. But IMO it's essentially the same problem as
>the CTOR:
>
>Base to Derived: Don't forget to write out the polymorphic stuff
> explicitly here or we're sunk.
>
>Derived to Base: Duh, I forgot.
Example?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: doug@monet.ads.com (Doug Morgan)
Date: 12 Dec 94 10:58:17 Raw View
In article <D0pAI6.Hrt@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
|In article <DOUG.94Dec11013112@monet.ads.com> doug@monet.ads.com (Doug Morgan) writes:
|>...
|>With C++, anytime you want some action (cleanup) to consistently
|>happen before executing the standard body of a function (wipe out that
|>object), you have to manually insert the code to do the action. That
|>is the correct C++ solution: sometimes actions just must be taken and
|>there is nothing you can do about it. There is nothing wrong with
|>designs requiring this solution, other than in C++ they force you into
|>a clumsy, error prone, on-your-honor coding practise. Such otherwise
|>reasonable designs are the reasons for before, after, and around
|>methods in other languages.
|
| Hm. But this is not quite right. Consider
|you want to do
|
| before(), body(), after()
|
|in order, where one or all three are polymorphic. Well, thats easy:
|
| void X::bba() { before(); body(); after(); }
|
|Now the fact that you may have several functions to wrap like
|this is perhaps a hassle, but a minor one: it can be done
|by the class designer and happens automatically in the derived
|class.
|
|The only place you _cant_ use this technique is in constructors
|and destructors. (And assignments) Why? Is that a bug in C++ or a logical
|inevitability?
It's just a feature of C++. You can't specify the actions *outside*
the code body. When C++ follows its rules for accessing code bodies
in constructors, destructors, and assignments, there is no place to
put the call to the action so it always get executed at the right
place. You just have to copy it over and over where it is needed.
C++ could be (but certainly won't be, and I think shouldn't be)
extended to handle these cases the way you would want.
Doug
----------
Doug Morgan, doug@ads.com, (415) 960-7444
Advanced Decision Systems (a division of Booz-Allen & Hamilton Inc.)
1500 Plymouth St., Mountain View, CA 94043-1230
----------
Author: scott@kaiwan.kaiwan.com (Scott Ellsworth)
Date: 13 Dec 1994 10:36:23 -0800 Raw View
The destructor thing caught me. I had a virtual function named Pop()
which was to handle taking apart my BAseStack class. Silly me, I tried
to put in a destructor only in the base class which called the virtual
Pop method. Simple to fix, really, but a pain when I did not know why
the sucker just did not work. Also brought home the lesson about making
abstract functions have pure virtual methods.
Scott
--
Scott Ellsworth scott@kaiwan.com
"When more and more people are out of work, unemployment results" - Coolidge?
"This is the aorta of the last person to ask me that" - Scott Adams
Author: bill_law@taligent.com (William A. Law)
Date: Tue, 13 Dec 1994 19:47:08 GMT Raw View
In article <DOUG.94Dec12105817@monet.ads.com>, doug@monet.ads.com (Doug
Morgan) wrote:
> C++ could be (but certainly won't be, and I think shouldn't be)
> extended to handle these cases the way you would want.
>
> Doug
> ----------
> Doug Morgan, doug@ads.com, (415) 960-7444
> Advanced Decision Systems (a division of Booz-Allen & Hamilton Inc.)
> 1500 Plymouth St., Mountain View, CA 94043-1230
> ----------
All I would want is to be able to reuse the inherited constructor but have
it do things in a manner appropriate for my derived class. A simplistic
example is:
struct base {
base ( ) { cout << "Constructing a " << className() << endl; }
virtual char *className() { return "base"; }
};
struct derived : base {
virtual char *className() { return "derived" };
};
derived d; // I want it to print "Constructing a derived"!!!!!
My solution to this problem is simple. Make className() a static member
function. This is more "correct" anyway (since the body of the function
never refers to "this" it obviously doesn't *need* a "this" pointer).
By making the function static, we remove the obstacle to calling the
virtual function in the base class constructor: Since there is no
(implied) derived pointer passed to className(), the fact that we have
only a base pointer is moot.
The only remaining problem is that static functions can't be virtual.
Whenever the idea of permitting this is brought up, it is usually shot
down with the argument: "Static virtuals aren't necessary. Since you
would need an object in order to determine which implementation to call,
just make the function a member function." I think the potential usage of
static virtuals in constructors exposes one important case where that
argument is deficient.
Anyway, that's my two cents.
Bill Law
Author: tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa)
Date: 10 Dec 1994 12:17:16 GMT Raw View
[text deleted]
: However, you may have a regression problem. A CTOR is supposed to leave
: an object in a valid initialization state. If now a post-CTOR is
: required for that, there may be other virtual functions the post-CTOR
: should not call because a derived object may rely on being fully formed.
: So you would need a post-post-CTOR, and so forth.
: More deeply, you need some way of controlling what assumptions a derived
: class can make in its virtuals when constructing. Right now we have the
: simplest answer: "None (because they don't get called anyways)". (IMO
: they should also have disallowed virtuals, forcing them to be called
: with scope-access syntax classname::foo() )
: Theoretically one could define a range of stages of construction, and
: tag virtual functions as requiring at least such-and-such a stage be
: reached. Which would be a very ambitious and "pure" solution, but would
: IMO be fairly rare to use. Calling "init()" "second_init()" and so forth
: by hand may not be as safe, but it sort of works for now.
Is it really impossible to implement virtual method calls in constructors?
If you think that the initialization process consists of several stages,
then the first stage could be some kind of basic initialization that
would allow virtual methods to be called (usually this would be setting up
the vtable pointer). In other words, before the statements in the body of
the constructor are executed (and before the constructors in the
initialization list are executed), the vtables (or equivalent) are
initialized.
For example:
class Parent
{
public:
Parent(x_) : x(x_)
// this constructor must receive some hidden parameter that tells it
// whether to initialize the vtable or not
{
Func();
}
virtual void Func()
{
x++;
}
private:
int x;
};
class OwnClass : public Parent
{
public:
OwnClass(int x_, int y_)
// vtable initialization would be here
: Parent(x_), value(y_)
{
}
virtual void Func()
{
Parent::Func();
y++;
}
};
int main()
{
// OwnClass::Func would get called in the constructor Parent::Parent.
OwnClass obj(1, 2);
return 0;
}
Tommi H yn l nmaa
Author: mikeryan@virtuoso.com (Mike Ryan)
Date: Sat, 10 Dec 1994 16:10:49 GMT Raw View
In article <D0KGr7.9sD@world.std.com>, tob@world.std.com (Tom O Breton) wrote:
> mikeryan@virtuoso.com (Mike Ryan) writes:
> > So, what about two new special member functions - a "post-constructor"
> > and a "pre-destructor"? The post-constructor would be called
>
> Note that the DTOR can already be virtual, so there's no great need.
>
> With the CTOR, you may have a point. It's less error-prone than
> "vanishing virtualness".
The destructor can be virtual, but it still can't call a virtual
function, which may still be useful (though clearly not as useful
as the constructor case). As a simple example, consider having
debug code to log the destruction of your objects. It'd be nice
to have your base class destructor say "cout << Identify();",
where Identify() is a virtual function overridden by each derived
class returning a string describing the class. Using a pre-destructor
as I proposed would mean only the base class would need to make
this call (in the pre-destructor), and the derived classes wouldn't
have to worry about it (or even know it's happening) in their
destructors.
> However, you may have a regression problem. A CTOR is supposed to leave
> an object in a valid initialization state. If now a post-CTOR is
> required for that, there may be other virtual functions the post-CTOR
> should not call because a derived object may rely on being fully formed.
> So you would need a post-post-CTOR, and so forth.
I don't think that's any more of a problem than the current situation,
where a class may require a call to a virtual function to finish
initializing. As it is now, it's not uncommon in this case for a
class to leave the object not-quite-initialized after the constructor,
then add stuff like "if (!initialized) CallVirtuals();" at the tops
of other member functions, or have a two-stage construct-then-call-
Create() mechanism. With a post-constructor mechanism, the post-
constructor alone would have to worry about calling virtual members
only if the object is in an appropriate state for them, localizing
the problem. A complex class could get into a multi-stage process
as you describe, but it's entirely up to the class designer (as it
should be) to resolve this - it's one of C++'s charms that you
always have more than enough rope to hang yourself with:-).
> More deeply, you need some way of controlling what assumptions a derived
> class can make in its virtuals when constructing. Right now we have the
> simplest answer: "None (because they don't get called anyways)". (IMO
> they should also have disallowed virtuals, forcing them to be called
> with scope-access syntax classname::foo() )
As I said (or implied) above, in a well-designed class all the
virtual functions shouldn't have to worry about it - the responsibility
for putting the object in the appropriate state would be with
the constructor(s) and post-constructor.
Mike Ryan
Virtuoso Software
Author: mikeryan@virtuoso.com (Mike Ryan)
Date: Sat, 10 Dec 1994 16:31:40 GMT Raw View
In article <3cc68c$4fn@tethys.otol.fi>,
tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) wrote:
> Is it really impossible to implement virtual method calls in constructors?
> If you think that the initialization process consists of several stages,
> then the first stage could be some kind of basic initialization that
> would allow virtual methods to be called (usually this would be setting up
> the vtable pointer). In other words, before the statements in the body of
> the constructor are executed (and before the constructors in the
> initialization list are executed), the vtables (or equivalent) are
> initialized.
This would significantly change the current semantics of construction
and destruction in the language, and I wouldn't want to do that. I'm
trying to propose something that would have no affect on code which
doesn't use it, and which I believe would be fairly simple to
implement in a compiler (assuming a reasonable declaration syntax,
which I still haven't come up with).
Mike Ryan
Virtuoso Software
Author: mikeryan@virtuoso.com (Mike Ryan)
Date: Sat, 10 Dec 1994 15:56:24 GMT Raw View
In article <ST.94Dec9192841@hobbes.mlc-ratingen.de>,
st@hobbes.mlc-ratingen.de (Stefan Tilkov) wrote:
> This is definitely an interesting idea. But I'm not sure what problem it fixes.
> I agree that most C++ programmers will have to learn the fact that a call to
> a virtual function will not work as they might expect, but once they know that,
> do they have a need for the feature you propose?
Personally, the only place I would be using this feature in my current
code, I wouldn't face the issue at all if it weren't for Visual C++'s
lack of support for RTTI, so it's not a burning issue personally. I've
simply seen the issue raised so many times on Usenet and CompuServe, by
people who described legitimate needs for calling virtual functions
when creating or destroying objects, that it seemed a worthwhile
suggestion. The readership here will correct me if I'm wrong:-).
> I can see many interesting applications for this idea - but I don't think it's
> really *necessary*. Thus, the chances for changing the standard to include this
> feature are probably pretty low (IMHO, of course). Also, consider that not
> everyone might profit from this extension, so a lot of people will disagree
> that it's worth lengthening the standardization process for this.
>
> You (and the commitee and the whole C++ community) have to make up their mind
> on what's more important: Adding nice and interesting features to the language
> or (finally) have a standard that helps to make compilers better. I vote for
> the second.
I did make up my mind, and I did vote for the second as well. My first
paragraph:
> In article <u34wkW1occWS075yn@virtuoso.com> mikeryan@virtuoso.com (Mike Ryan) writes:
> >I'd like to make a proposal for a C++ language extension - I don't
> >think this is worth holding up the current standardization effort
> >for, but just something to consider for a later update to the
> >standard.
I want to see the standard finalized so the C++ community can get
on with their lives - but I'm sure there will be later editions of
the standard, as there are still ways to improve the language.
> Besides, I think the behaviour you want could be achieved by using a combination
> of a template class and a base class that the objects that should make use of
> this feature inherit from, but I'd have to think harder about that.
I'm not quite sure what you have in mind here (as a Visual C++ user,
I've just recently been able to start actually using templates, so
it's an area of the language I'm less experienced with). If we can
come up with an idiom to let class designers achieve virtual functions
calls at creation and destruction time without burdening their clients
(as, say, MFC does with its construct-then-call-Create business),
great.
Mike Ryan
Virtuoso Software
Author: tob@world.std.com (Tom O Breton)
Date: Sat, 10 Dec 1994 23:39:36 GMT Raw View
tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
> Is it really impossible to implement virtual method calls in constructors?
No, in fact what you ask could be done in several ways.
The problem is that the issue is more than just enabling virtual calls.
They also should be called on an adequately-constructed(*) object. For
example:
class OwnClass : public Parent
{
OwnClass( char* filename )
: fd( fopen( filename, "rw" ) )
{ };
FILE* fd;
virtual void
Func()
{
puts( fd, "OwnClass" );
};
};
...Func is going to think it's writing to a valid file when it's not.
Worst case (this happened to me once) it will write to the boot sector 0
on your hard drive and crash your system so totally you'll need to
reformat your HD.
* I say adequately-constructed instead of fully-constructed to admit the
possibility that some virtual functions don't use everything in their
derived class.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 10 Dec 1994 21:08:01 GMT Raw View
I've certainly always wanted this. CLOS can do it. I don't know why
C++ can't.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 10 Dec 1994 21:08:57 GMT Raw View
Newsgroups: comp.std.c++,comp.lang.c++
Path: mlb.sticomet.com!psinntp!psinntp!psinntp!sed.psrw.com!eff!news.kei.com!news.mathworks.com!udel!gatech!swrinde!pipex!uunet!world!tob
From: tob@world.std.com (Tom O Breton)
Reply-To: tob@world.std.com
Organization: BREnterprises
References: <u34wkW1occWS075yn@virtuoso.com>
Date: Fri, 9 Dec 1994 23:18:43 GMT
X-Posted-By: My own casual posting program
Lines: 36
Xref: mlb.sticomet.com comp.std.c++:9142 comp.lang.c++:78071
mikeryan@virtuoso.com (Mike Ryan) writes:
> It's a common desire to be able to call a virtual function at the
> time an object is constructed (or destroyed). As a matter of fact,
[...]
> So, what about two new special member functions - a "post-constructor"
> and a "pre-destructor"? The post-constructor would be called
Note that the DTOR can already be virtual, so there's no great need.
With the CTOR, you may have a point. It's less error-prone than
"vanishing virtualness".
However, you may have a regression problem. A CTOR is supposed to leave
an object in a valid initialization state. If now a post-CTOR is
required for that, there may be other virtual functions the post-CTOR
should not call because a derived object may rely on being fully formed.
So you would need a post-post-CTOR, and so forth.
More deeply, you need some way of controlling what assumptions a derived
class can make in its virtuals when constructing. Right now we have the
simplest answer: "None (because they don't get called anyways)". (IMO
they should also have disallowed virtuals, forcing them to be called
with scope-access syntax classname::foo() )
Theoretically one could define a range of stages of construction, and
tag virtual functions as requiring at least such-and-such a stage be
reached. Which would be a very ambitious and "pure" solution, but would
IMO be fairly rare to use. Calling "init()" "second_init()" and so forth
by hand may not be as safe, but it sort of works for now.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Well, you just have to call them in the opposite order.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sun, 11 Dec 1994 04:47:32 GMT Raw View
In article <D0KGr7.9sD@world.std.com> tob@world.std.com writes:
>mikeryan@virtuoso.com (Mike Ryan) writes:
>> It's a common desire to be able to call a virtual function at the
>> time an object is constructed (or destroyed). As a matter of fact,
> [...]
>> So, what about two new special member functions - a "post-constructor"
>> and a "pre-destructor"? The post-constructor would be called
I don't think there is any problem with constructors.
However, there _is_ a nasty problem that occurs with destructors.
Typically:
struct A {
virtual ~A(){ cleanup(); }
virtual void cleanup();
};
struct B : A { void cleanup(); };
The problem is that destruction may require full polymorphic
behaviour of "cleanup". But as you can see, I forgot
to write:
B::~B() { cleanup(); }
to make the destructor of B invoke the B cleanup routine.
In the destructor of A, only the A cleanup routine is invoked,
because the B part of the object is already destroyed.
The fact that there _appears_ to be a problem in the C++
language here suggests to me that one ought to look at
the design requiring derivers to write the destructor
out each time. Often, such apparent problems in C++ turn
out to be program design problems, and often there
is a good reason why the extra work is actually required and makes
sense in a wider context.
That is -- while C++ does have some language design faults,
most of it is coherent and there are usually very good reasons
for everything. Sometimes these are engineering decisions
taken by Bjarne or the committee, and sometimes they are
the inevitable consequences of more fundamental principles.
It is not always clear what the consequences are though:
it takes work, time, and effort to derive useful theorems
from axioms. For example it took me a year to figure out
that the solution to the virtual base initialisation
problem was that there was no problem in C++ but that
one could instead _derive_ a constraint on programming
style for which the problem evaporates. (Ensure virtual
bases have default constructors -- preferably they
are interface classes only with no data members).
In the case of the example above I'd be very interested in
an analysis of what is "wrong" with a design which
requires a destructor to be written out each time:
I've run into this problem a few times and the kind of
designs for which the problem exist have always smelt
a bit funny -- but I have not been able to pin down
what is wrong with them, and what the "correct" solution
to the kind of problem that design was intended for actually is.
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: doug@monet.ads.com (Doug Morgan)
Date: 11 Dec 94 01:31:12 Raw View
In article <D0Mqn8.LxA@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
|...
| struct A {
| virtual ~A(){ cleanup(); }
| virtual void cleanup();
| };
| struct B : A { void cleanup(); };
|...
|In the case of the example above I'd be very interested in
|an analysis of what is "wrong" with a design which
|requires a destructor to be written out each time:
|I've run into this problem a few times and the kind of
|designs for which the problem exist have always smelt
|a bit funny -- but I have not been able to pin down
|what is wrong with them, and what the "correct" solution
|to the kind of problem that design was intended for actually is.
With C++, anytime you want some action (cleanup) to consistently
happen before executing the standard body of a function (wipe out that
object), you have to manually insert the code to do the action. That
is the correct C++ solution: sometimes actions just must be taken and
there is nothing you can do about it. There is nothing wrong with
designs requiring this solution, other than in C++ they force you into
a clumsy, error prone, on-your-honor coding practise. Such otherwise
reasonable designs are the reasons for before, after, and around
methods in other languages.
Doug
----------
Doug Morgan, doug@ads.com, (415) 960-7444
Advanced Decision Systems (a division of Booz-Allen & Hamilton Inc.)
1500 Plymouth St., Mountain View, CA 94043-1230
----------
Author: mmediko@hubcap.clemson.edu (Medikonda Muralidhar)
Date: 11 Dec 1994 17:21:41 GMT Raw View
Please pardon my ignorance... but then I am not a very experienced
C++ programmer either.
Could someone give couple of situations where one would want to
call a virtual function within a CTOR or DTOR.
Appreciate your responses!
Murali Medikonda
mikeryan@virtuoso.com (Mike Ryan) writes:
>I'd like to make a proposal for a C++ language extension - I don't
>think this is worth holding up the current standardization effort
>for, but just something to consider for a later update to the
>standard.
>It's a common desire to be able to call a virtual function at the
>time an object is constructed (or destroyed). As a matter of fact,
>probably the most common mistake made by C++ programmers (generally
>at just about the time they think they're starting to understand
>the language) is to call a virtual function in their constructor and
>get surprised when it doesn't call the derived member function. It
>makes perfect sense once it's explained that the derived object
>doesn't exist yet, but that doesn't change the fact that it is often
>useful to be able to apply virtual member functions to an object
>automatically when it is created, before it is used (and,
>correspondingly, when it is destroyed).
>So, what about two new special member functions - a "post-constructor"
>and a "pre-destructor"? The post-constructor would be called
>immediately after construction of the object is complete, before
>the object is used. Since the object is completely constructed,
>virtual functions would apply in the intuitive manner. Similarly,
>the pre-destructor would be called just before object destruction,
>when the object is still intact. Neither the post-constructor nor
>the pre-destructor would take any arguments or return a value.
>I'm not going to propose a syntax for declaring these special
>member functions here - I'm primarily interested in feedback on
>the concept, and no obvious syntax leaps out at me (obviously,
>coming up with a reasonable syntax is a pre-requisite for this
>proposal to go beyond the "that's an interesting idea" stage).
>Opinions?
>Mike Ryan
>Virtuoso Software
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 11 Dec 1994 18:08:47 GMT Raw View
tohoyn@janus.otol.fi (Tommi H|yn{l{nmaa) writes:
> Is it really impossible to implement virtual method calls in constructors?
No, in fact what you ask could be done in several ways.
Yes, the vtbl isn't set up so it ends up getting called like it was a
non-virtual.
The problem is that the issue is more than just enabling virtual calls.
They also should be called on an adequately-constructed(*) object. For
example:
class OwnClass : public Parent
{
OwnClass( char* filename )
: fd( fopen( filename, "rw" ) )
{ };
FILE* fd;
virtual void
Func()
{
puts( fd, "OwnClass" );
};
};
...Func is going to think it's writing to a valid file when it's not.
Worst case (this happened to me once) it will write to the boot sector 0
on your hard drive and crash your system so totally you'll need to
reformat your HD.
* I say adequately-constructed instead of fully-constructed to admit the
possibility that some virtual functions don't use everything in their
derived class.
Tom
I don't see how this example illustrates calling a virtual from within
a constructor. You don't show Func being called even. If you've
really got some kind of work around for using virtual functions to
initialize an object, I'd really like to know about it, but your
example doesn't give me a clue.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts)
Date: 11 Dec 1994 18:15:12 GMT Raw View
The fact that there _appears_ to be a problem in the C++
language here suggests to me that one ought to look at
the design requiring derivers to write the destructor
out each time. Often, such apparent problems in C++ turn
out to be program design problems, and often there
is a good reason why the extra work is actually required and makes
sense in a wider context.
I disagree. It's a bad thing to make classes more heavyweight than
needed. You should only have to write a destructor if you've
allocated some kind of resource in you constructor (called new for
example). You shouldn't have to know or care if your parent class has
an explicit destructor or not.
--
____________________________________________________________________________
___________
Malcolm McRoberts
STI
1225 Evans Rd.
Melboune, Fl. 32904
Email: malcolm@sticomet.com
Ph: (407) 723-3999
Fax: (407) 676-4510
____________________________________________________________________________
___________
Author: tob@world.std.com (Tom O Breton)
Date: Mon, 12 Dec 1994 04:38:31 GMT Raw View
malcolm@xenon.mlb.sticomet.com (Malcolm McRoberts) writes:
> I don't see how this example illustrates calling a virtual from within
> a constructor. You don't show Func being called even.
Two answers for that:
* You shouldn't have to deal with that knowledge in order to make
it safe.
* I was continuing the example in the message I was responding to,
and it's clear in that context.
> If you've really got some kind of work around for using virtual
> functions to initialize an object, I'd really like to know about it, but
> your example doesn't give me a clue.
The workaround is uninteresting, error-prone, but easy: Call an init()
function by hand. You will have to deal with details the compiler should
control, but you can initialize in as many stages as you like.
If you meant "workaround for automatically initing the vtable before the
CTOR in C++ as it stands" then you misunderstood me.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: tob@world.std.com (Tom O Breton)
Date: Mon, 12 Dec 1994 04:38:35 GMT Raw View
maxtal@physics.su.OZ.AU (John Max Skaller) writes:
> I don't think there is any problem with constructors.
Let me see if I understand your point:
In theory everything you need to do in constructing an object
can be done at some stage in the CTOR chain without using
polymorphism.
Is that it?
While true, consider the case where a would-be virtual call is embedded
in control logic in the base's CTOR.
It may force the programmer to laden the base with do-too-little CTORs
whose functionality has to be completed in every derived class. Or
alternatively to use awkward, overexposed mechanisms like passing a
pointer-to-function down the CTOR chain.
> The problem is that destruction may require full polymorphic
> behaviour of "cleanup". But as you can see, I forgot
> to write:
>
> B::~B() { cleanup(); }
>
> to make the destructor of B invoke the B cleanup routine.
> In the destructor of A, only the A cleanup routine is invoked,
> because the B part of the object is already destroyed.
Ah, I see your point now. But IMO it's essentially the same problem as
the CTOR:
Base to Derived: Don't forget to write out the polymorphic stuff
explicitly here or we're sunk.
Derived to Base: Duh, I forgot.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower
Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Mon, 12 Dec 1994 13:51:41 GMT Raw View
In article <DOUG.94Dec11013112@monet.ads.com> doug@monet.ads.com (Doug Morgan) writes:
>In article <D0Mqn8.LxA@ucc.su.OZ.AU> maxtal@physics.su.OZ.AU (John Max Skaller) writes:
>|...
>| struct A {
>| virtual ~A(){ cleanup(); }
>| virtual void cleanup();
>| };
>| struct B : A { void cleanup(); };
>|...
>
>With C++, anytime you want some action (cleanup) to consistently
>happen before executing the standard body of a function (wipe out that
>object), you have to manually insert the code to do the action. That
>is the correct C++ solution: sometimes actions just must be taken and
>there is nothing you can do about it. There is nothing wrong with
>designs requiring this solution, other than in C++ they force you into
>a clumsy, error prone, on-your-honor coding practise. Such otherwise
>reasonable designs are the reasons for before, after, and around
>methods in other languages.
Hm. But this is not quite right. Consider
you want to do
before(), body(), after()
in order, where one or all three are polymorphic. Well, thats easy:
void X::bba() { before(); body(); after(); }
Now the fact that you may have several functions to wrap like
this is perhaps a hassle, but a minor one: it can be done
by the class designer and happens automatically in the derived
class.
The only place you _cant_ use this technique is in constructors
and destructors. (And assignments) Why? Is that a bug in C++ or a logical
inevitability?
--
JOHN (MAX) SKALLER, INTERNET:maxtal@suphys.physics.su.oz.au
Maxtal Pty Ltd,
81A Glebe Point Rd, GLEBE Mem: SA IT/9/22,SC22/WG21
NSW 2037, AUSTRALIA Phone: 61-2-566-2189
Author: mikeryan@virtuoso.com (Mike Ryan)
Date: Fri, 9 Dec 1994 11:35:20 GMT Raw View
I'd like to make a proposal for a C++ language extension - I don't
think this is worth holding up the current standardization effort
for, but just something to consider for a later update to the
standard.
It's a common desire to be able to call a virtual function at the
time an object is constructed (or destroyed). As a matter of fact,
probably the most common mistake made by C++ programmers (generally
at just about the time they think they're starting to understand
the language) is to call a virtual function in their constructor and
get surprised when it doesn't call the derived member function. It
makes perfect sense once it's explained that the derived object
doesn't exist yet, but that doesn't change the fact that it is often
useful to be able to apply virtual member functions to an object
automatically when it is created, before it is used (and,
correspondingly, when it is destroyed).
So, what about two new special member functions - a "post-constructor"
and a "pre-destructor"? The post-constructor would be called
immediately after construction of the object is complete, before
the object is used. Since the object is completely constructed,
virtual functions would apply in the intuitive manner. Similarly,
the pre-destructor would be called just before object destruction,
when the object is still intact. Neither the post-constructor nor
the pre-destructor would take any arguments or return a value.
I'm not going to propose a syntax for declaring these special
member functions here - I'm primarily interested in feedback on
the concept, and no obvious syntax leaps out at me (obviously,
coming up with a reasonable syntax is a pre-requisite for this
proposal to go beyond the "that's an interesting idea" stage).
Opinions?
Mike Ryan
Virtuoso Software
Author: st@hobbes.mlc-ratingen.de (Stefan Tilkov)
Date: 09 Dec 1994 18:28:41 GMT Raw View
In article <u34wkW1occWS075yn@virtuoso.com> mikeryan@virtuoso.com (Mike Ryan) writes:
>Newsgroups: comp.std.c++,comp.lang.c++
>From: mikeryan@virtuoso.com (Mike Ryan)
>Followup-To: comp.std.c++
>Reply-To: mikeryan@virtuoso.com
>Organization: Virtuoso Software
>Date: Fri, 9 Dec 1994 11:35:20 GMT
>
>I'd like to make a proposal for a C++ language extension - I don't
>think this is worth holding up the current standardization effort
>for, but just something to consider for a later update to the
>standard.
>
>It's a common desire to be able to call a virtual function at the
>time an object is constructed (or destroyed). As a matter of fact,
>probably the most common mistake made by C++ programmers (generally
>at just about the time they think they're starting to understand
>the language) is to call a virtual function in their constructor and
>get surprised when it doesn't call the derived member function. It
>makes perfect sense once it's explained that the derived object
>doesn't exist yet, but that doesn't change the fact that it is often
>useful to be able to apply virtual member functions to an object
>automatically when it is created, before it is used (and,
>correspondingly, when it is destroyed).
>
>So, what about two new special member functions - a "post-constructor"
>and a "pre-destructor"? The post-constructor would be called
>immediately after construction of the object is complete, before
>the object is used. Since the object is completely constructed,
>virtual functions would apply in the intuitive manner. Similarly,
>the pre-destructor would be called just before object destruction,
>when the object is still intact. Neither the post-constructor nor
>the pre-destructor would take any arguments or return a value.
>
>I'm not going to propose a syntax for declaring these special
>member functions here - I'm primarily interested in feedback on
>the concept, and no obvious syntax leaps out at me (obviously,
>coming up with a reasonable syntax is a pre-requisite for this
>proposal to go beyond the "that's an interesting idea" stage).
>
>Opinions?
>
>Mike Ryan
>Virtuoso Software
This is definitely an interesting idea. But I'm not sure what problem it fixes.
I agree that most C++ programmers will have to learn the fact that a call to
a virtual function will not work as they might expect, but once they know that,
do they have a need for the feature you propose?
I can see many interesting applications for this idea - but I don't think it's
really *necessary*. Thus, the chances for changing the standard to include this
feature are probably pretty low (IMHO, of course). Also, consider that not
everyone might profit from this extension, so a lot of people will disagree
that it's worth lengthening the standardization process for this.
You (and the commitee and the whole C++ community) have to make up their mind
on what's more important: Adding nice and interesting features to the language
or (finally) have a standard that helps to make compilers better. I vote for
the second.
Besides, I think the behaviour you want could be achieved by using a combination
of a template class and a base class that the objects that should make use of
this feature inherit from, but I'd have to think harder about that.
--
__________________________________________________________________
Stefan Tilkov MLC Ratingen, Germany
st@mlc-ratingen.de
Phone +49 (0) 2102 8506 20
Fax +49 (0) 2102 8506 30
Author: tob@world.std.com (Tom O Breton)
Date: Fri, 9 Dec 1994 23:18:43 GMT Raw View
mikeryan@virtuoso.com (Mike Ryan) writes:
> It's a common desire to be able to call a virtual function at the
> time an object is constructed (or destroyed). As a matter of fact,
[...]
> So, what about two new special member functions - a "post-constructor"
> and a "pre-destructor"? The post-constructor would be called
Note that the DTOR can already be virtual, so there's no great need.
With the CTOR, you may have a point. It's less error-prone than
"vanishing virtualness".
However, you may have a regression problem. A CTOR is supposed to leave
an object in a valid initialization state. If now a post-CTOR is
required for that, there may be other virtual functions the post-CTOR
should not call because a derived object may rely on being fully formed.
So you would need a post-post-CTOR, and so forth.
More deeply, you need some way of controlling what assumptions a derived
class can make in its virtuals when constructing. Right now we have the
simplest answer: "None (because they don't get called anyways)". (IMO
they should also have disallowed virtuals, forcing them to be called
with scope-access syntax classname::foo() )
Theoretically one could define a range of stages of construction, and
tag virtual functions as requiring at least such-and-such a stage be
reached. Which would be a very ambitious and "pure" solution, but would
IMO be fairly rare to use. Calling "init()" "second_init()" and so forth
by hand may not be as safe, but it sort of works for now.
Tom
--
tob@world.std.com
TomBreton@delphi.com: Author of The Burning Tower