Topic: Calling virtual functions in constructors
Author: laurie.cheers@btinternet.com (Laurie Cheers)
Date: Sat, 23 Oct 2004 02:55:35 GMT Raw View
alfps@start.no (Alf P. Steinbach) wrote in message news:<41772aa0.677847031@news.individual.net>...
> * Laurie Cheers:
>>> There are a lot of situations in which the ability to call virtual
>>> functions from a constructor (properly) would be useful.
>
> It's a shame nobody have mentioned the FAQ.
>
> <url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq
> -23.4>
I'm not sure what your point is. Ok, that FAQ (which is interesting, thanks)
suggests the workaround that I was talking about, amongst other things. But
my point is that this issue comes up very frequently. The workarounds here all
require a lot of rewriting and refactoring of code - hard work, considering
that the concept itself isn't conceptually hard.
Hence my suggestion can be seen as a shortcut way of making a "wrapper" class,
whose methods simply call the corresponding methods in the object it's
wrapping.
Like this:
class ActualInterface
{
virtual void Method1() = 0;
virtual void Method2() = 0;
}
class Wrapper: public ActualInterface
{
protected:
ActualInterface* Actual;
public:
Wrapper(...)
{
Actual = new ActualObject(...);
//more initialisation...
}
void Method1() { Actual->Method1(); }
void Method2() { Actual->Method2(); }
};
The advantage of this? When you construct a Wrapper object, its constructor is
acting as a factory function; it creates an ActualObject (or whatever child of
ActualInterface you want, really). And _after that's finished_, it can do
further initialisation on it, including calling its virtual functions.
Now, obviously you can do this in C++ right now, but it's an incredibly tedious
job creating a wrapper for every class that needs it, and keeping the wrapper
in sync with the class itself. Having a Single Point Of Truth is a valuable
thing.
--
Laurie Cheers
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: bloodymir.crap@gmx.net (Frank Birbacher)
Date: Sat, 23 Oct 2004 11:44:12 GMT Raw View
Hi!
Laurie Cheers wrote:
> Does this make sense? I think it would be a very convenient
> syntax for an extremely powerful concept. For example, notice
> that it allows Base's constructor to act, essentially, as a
> factory function: if you have several child classes of
> Base::Impl, it can decide which one to instantiate based on
> the parameters it's given. Very useful.
So, if the base class knows which child it uses, how is your
approach different from calling the child methods without virtual
dispatch.
The crux is, that the base ctor shall not know the concrete type
instantiated, and still call the methods of the derived class
through virtual dispatch.
Frank
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Sun, 24 Oct 2004 20:44:58 GMT Raw View
Alf P. Steinbach wrote:
=
> It's a shame nobody have mentioned the FAQ.
>
> <url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4>
That's too l33t for production code.
"Any problem in computer science can be solved by
adding another layer of indirection".
There's too much of that in C++ already.
The "pimpl" hack leads to code bloat and maintenance
headaches. We don't need to introduce more of that sort of
thing. Try to come up with something that doesn't involve
adding another layer.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Michiel.Salters@logicacmg.com (Michiel Salters)
Date: Mon, 25 Oct 2004 18:27:32 GMT Raw View
nagle@animats.com (John Nagle) wrote in message news:<snTdd.33272$QJ3.8424@newssvr21.news.prodigy.com>...
> Alf P. Steinbach wrote:
> =
> > It's a shame nobody have mentioned the FAQ.
> >
> > <url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4>
>
> That's too l33t for production code.
>
> "Any problem in computer science can be solved by
> adding another layer of indirection".
>
> There's too much of that in C++ already.
> The "pimpl" hack leads to code bloat and maintenance
> headaches. We don't need to introduce more of that sort of
> thing. Try to come up with something that doesn't involve
> adding another layer.
The alternative to introducing another layer is stuffing everything
in one layer. It sounds like you're advocating spaghetti design.
Besides, when 90% of a design space does not need a feature,
always including that feature (instead of using it by selective
indirection) complicates the overall design.
Calling derived functions (including overrides of virtual functions)
as part of base class contruction is definitely such a feature; I
doubt that 1%, let alone 10% of classes needs such a feature.
Besides, Factory is a standard pattern from the pattern book. That's
a hardcover, so you can use it to throw at the colleague who complains
it's too l33t for production code.
HTH,
Michiel Salters
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Mon, 25 Oct 2004 18:27:41 GMT Raw View
In article <snTdd.33272$QJ3.8424@newssvr21.news.prodigy.com>,
nagle@animats.com (John Nagle) wrote:
> Alf P. Steinbach wrote:
> =
> > It's a shame nobody have mentioned the FAQ.
> >
> > <url:
> > http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4>
>
> That's too l33t for production code.
Solving the problem is "too l33t"? Answering the question is "too l33t"?
It sounds like you want this to "just work"; i.e., base class
constructors to call derived member functions. That may not be "l33t",
but it would be wrong, and lead to lots of busted code.
> "Any problem in computer science can be solved by
> adding another layer of indirection".
>
> There's too much of that in C++ already.
> The "pimpl" hack leads to code bloat and maintenance
> headaches.
Odd comment, since the pimpl "hack", if used properly, reduces
maintainance headaches.
> We don't need to introduce more of that sort of
> thing. Try to come up with something that doesn't involve
> adding another layer.
Given that the solutions pointed out by Alf are apparently unacceptable
to you, perhaps _you_ should come up with something that doesn't involve
adding another layer.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: laurie.cheers@btinternet.com (Laurie Cheers)
Date: Mon, 25 Oct 2004 20:26:56 GMT Raw View
bloodymir.crap@gmx.net (Frank Birbacher) wrote in message news:<2tujthF254eb1U1@uni-berlin.de>...
> Hi!
>
> Laurie Cheers wrote:
> > Does this make sense? I think it would be a very convenient
> > syntax for an extremely powerful concept. For example, notice
> > that it allows Base's constructor to act, essentially, as a
> > factory function: if you have several child classes of
> > Base::Impl, it can decide which one to instantiate based on
> > the parameters it's given. Very useful.
>
> So, if the base class knows which child it uses, how is your
> approach different from calling the child methods without virtual
> dispatch.
Because it doesn't have to know which.
> The crux is, that the base ctor shall not know the concrete type
> instantiated, and still call the methods of the derived class
> through virtual dispatch.
Ok then. Create a Base::Impl child object externally, and pass it into the
Base object, if that bothers you.
Base
{
void SetImpl( Impl* impl ) { Impl = impl; }
}
If nothing else, that approach is surely more efficient than putting a
switch statement in every function that you want to make pseudo-virtual.
--
Laurie Cheers
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: laurie.cheers@btinternet.com (Laurie Cheers)
Date: Thu, 21 Oct 2004 01:55:29 GMT Raw View
laurie.cheers@btinternet.com (Laurie Cheers) wrote in message news:<c26006e0.0410070851.637af10d@posting.google.com>...
> There are a lot of situations in which the ability to call virtual functions
> from a constructor (properly) would be useful.
> I'd suggest the virtual keyword could be reused for this call. Something like:
>
> class Base
> {
> Base()
> {
> printf("Base's display prints: ");
> display();
> virtual; // call the child's contructor here
> printf("\nChild's display prints: ");
> display();
> }
>
> display() { printf("Base"); }
> }
I thought of a more versatile solution to this problem.
John Nagle suggested (something I assume was meant to be)
a way around it, namely using two objects which reference
each other. The base object constructs what I'll call a
"virtual implentation" object, and it can then call
that implementation's virtual functions successfully.
This seems like a very useful concept, but in C++ as it
stands, it seems clear that it would be awkward to use.
The implementation has no privileged access to the base
object - for most purposes they would probably have to
be declared as friends - and the implementation has to
explicitly refer to the base any time it wants to use it.
I think the language should offer a kind of syntactic
sugar for this concept. Something like:
class Base
{
protected:
virtual class Impl;
virtual(Impl) void ImplFunc() { ... BaseFunc(); ... }
void BaseFunc() { ... }
public:
Base()
{
Impl = new Impl();
ImplFunc();
}
}
The intent, in case it's not clear, is that this would declare two
classes, Base and Base::Impl.
The line "virtual class Impl;" declares two things: a friend class
Impl, and a member,
"Impl* Impl".
The line "virtual(Impl) void ImplFunc() { ... BaseFunc(); ... }"
declares that the class Impl
contains a function ImplFunc, and that the class Base contains a
similar function which simply
calls "Impl->BaseFunc();". The concept is that ImplFunc is actually a
virtual function of
Base, which just happens to be defined in Impl.
Needless to say, writing virtual(Foo) is only legal for classes that
have been declared
using the "virtual class Foo" syntax.
So the translation, in current C++, is something like:
class Base
{
protected:
friend class Impl;
class Impl
{
public:
virtual void ImplFunc(Base* base) { ... base->BaseFunc(); ... }
}
Impl* m_Impl;
void ImplFunc() { m_Impl->ImplFunc(this); }
void BaseFunc() { ... }
public:
Base()
{
m_Impl = new Impl();
ImplFunc();
}
}
The sugared version has several differences:
First, it's obviously shorter and IMO clearer.
Second, Base::Impl is treated in most ways like a child of
Base. You don't need to declare it as a friend in order to
let it use your protected members; and in its functions
you don't have to explicitly refer to a Base object in
order for it to know what you mean.
(Compare
virtual void ImplFunc(Base* base) { ... base->BaseFunc(); ... }
with
virtual(Impl) void ImplFunc() { ... BaseFunc(); ... }
)
Third, and perhaps most contentious, the virtual functions
that Base::Impl inherits from Base are special. They cannot
be called directly - only via the special "forwarding"
functions in Base, which are created automatically as part of
the virtual(Impl) declaration.
Does this make sense? I think it would be a very convenient
syntax for an extremely powerful concept. For example, notice
that it allows Base's constructor to act, essentially, as a
factory function: if you have several child classes of
Base::Impl, it can decide which one to instantiate based on
the parameters it's given. Very useful.
--
Laurie Cheers
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Thu, 21 Oct 2004 06:55:03 GMT Raw View
* Laurie Cheers:
> * Laurie Cheers:
> > There are a lot of situations in which the ability to call virtual functions
> > from a constructor (properly) would be useful.
> > I'd suggest the virtual keyword could be reused for this call. Something like:
> >
> > class Base
> > {
> > Base()
> > {
> > printf("Base's display prints: ");
> > display();
> > virtual; // call the child's contructor here
> > printf("\nChild's display prints: ");
> > display();
> > }
> >
> > display() { printf("Base"); }
> > }
>
> I thought of a more versatile solution to this problem.
It's a shame nobody have mentioned the FAQ.
<url: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4>
The FAQ isn't complete in that it doesn't address the general problem of
post-construction / pre-destruction, but although that kettle of fish
rattles a lot whenever it's mentioned (how does it know?) it always, IMHO,
turns out to be empty (this is a mystery of quantum kettle-dynamics).
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: laurie.cheers@btinternet.com (Laurie Cheers)
Date: Thu, 7 Oct 2004 18:10:48 GMT Raw View
There are a lot of situations in which the ability to call virtual functions
from a constructor (properly) would be useful.
Would it be possible for a constructor to call the constructor of its child?
When that call returns, the child would have finished being constructed, so
the parent constructor would be able to use the proper virtual functions.
I'd suggest the virtual keyword could be reused for this call. Something like:
class Base
{
Base()
{
printf("Base's display prints: ");
display();
virtual; // call the child's contructor here
printf("\nChild's display prints: ");
display();
}
display() { printf("Base"); }
}
class Child: public Base
{
Child()
{
print("\nChild's constructor runs");
}
display() { printf("Child"); }
}
Needless to say, constructing a Child object should output
Base's display prints: Base
Child's constructor runs
Child's display prints: Child
--
Laurie Cheers
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Thu, 7 Oct 2004 20:57:17 GMT Raw View
Laurie Cheers wrote:
> There are a lot of situations in which the ability to call virtual functions
> from a constructor (properly) would be useful.
>
> Would it be possible for a constructor to call the constructor of its child?
> When that call returns, the child would have finished being constructed, so
> the parent constructor would be able to use the proper virtual functions.
There's a germ of a good idea here.
Constructors often do two things - they place the object into
a valid state, and then they do some other initialization.
Currently, there's no distinction in the language between those
two functions. Perhaps there should be.
There's a common idiom that's not strictly legal but is widely
used, in which a constructor allocates another object and creates
a backlink to it.
class A:
class B {
A& parent;
public:
B(A& p)
: parent(p) {} // set backlink
};
class A {
B* child;
public:
A()
: B(0)
{ child = new B(*this); } // wrong, but common
};
That's formally undefined behavior, but it's quite common.
As the user sees it, "construction" was complete before the "new",
or the user knows that B's constructor doesn't invoke any
functions of A, so it is safe to do this.
We allow constructors to call functions of the object being
constructed, which really ought to be considered an error,
since the object hasn't been constructed yet.
So maybe there should be a way to say "construction is finished"
in a constructor. After that, but not before, you can call
other functions of the object, virtual or not. That's what
the proposed executable use of "virtual" really says.
But a more block-oriented syntax would be appropriate. Something
like try/catch, but with different keywords.
Functions that really need to be called during construction
ought to be designated somehow as "constructor time functions".
This is probably too radical, but it's good preparation for
design by contract, where the inside/outside distinction really
matters.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alfps@start.no (Alf P. Steinbach)
Date: Thu, 7 Oct 2004 22:23:04 GMT Raw View
* John Nagle:
>
> There's a common idiom that's not strictly legal but is widely
> used, in which a constructor allocates another object and creates
> a backlink to it.
>
> class A:
>
> class B {
> A& parent;
> public:
> B(A& p)
> : parent(p) {} // set backlink
> };
>
> class A {
> B* child;
> public:
> A()
> : B(0)
> { child = new B(*this); } // wrong, but common
> };
>
> That's formally undefined behavior, but it's quite common.
What is in your opinion UB (I can't see any UB in this code)?
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: tannhauser86549spam@free.fr (=?windows-1252?Q?Falk_Tannh=E4user?=)
Date: Thu, 7 Oct 2004 22:44:55 GMT Raw View
John Nagle wrote:
> There's a common idiom that's not strictly legal but is widely
> used, in which a constructor allocates another object and creates
> a backlink to it.
>=20
> class A:
>=20
> class B {
> A& parent;
> public:
> B(A& p)
> : parent(p) {} // set backlink
> };
>=20
> class A {
> B* child;
> public:
> A()
> : B(0)
> { child =3D new B(*this); } // wrong, but common
Not sure what the ': B(0)' is good for - I assume this constructor
should read
A() : child(new B(*this)) {}=20
> };
>=20
> That's formally undefined behavior, but it's quite common.
> As the user sees it, "construction" was complete before the "new",
> or the user knows that B's constructor doesn't invoke any
> functions of A, so it is safe to do this.
As far as I understand =A7 3.8/6, there is no UB involved here.
"Similarly, before the lifetime of an object has started but after
the storage which the object will occupy has been allocated or,
after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, any lvalue which
refers to the original object may be used but only in limited ways.
Such an lvalue refers to allocated storage (3.7.3.2), and using the
properties of the lvalue which do not depend on its value is well-
defined. If an lvalue-to-rvalue conversion (4.1) is applied to such
an lvalue, the program has undefined behavior; if the original object
will be or was of a non-POD class type, the program has undefined
behavior if:
=97 the lvalue is used to access a non-static data member or call a
non-static member function of the object, or
=97 the lvalue is implicitly converted (4.10) to a reference to a
base class type, or
=97 the lvalue is used as the operand of a static_cast (5.2.9)
(except when the conversion is ultimately to char& or unsigned char&)=
,
or
=97 the lvalue is used as the operand of a dynamic_cast (5.2.7) or as
the operand of typeid."
Binding the lvalue '*this' referring to a not yet fully constructed
object to the reference 'A& B::parent' doesn't violate these rules -
it would only be undefined if 'B::parent' were a reference to a base
class of A.
Falk
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Fri, 8 Oct 2004 17:37:54 GMT Raw View
Falk Tannh=E4user wrote:
> John Nagle wrote:
>=20
>> There's a common idiom that's not strictly legal but is widely
>> used, in which a constructor allocates another object and creates
>> a backlink to it.
>>
..
> As far as I understand =A7 3.8/6, there is no UB involved here.
>=20
> "Similarly, before the lifetime of an object has started but after
> the storage which the object will occupy has been allocated or,
> after the lifetime of an object has ended and before the storage
> which the object occupied is reused or released, any lvalue which
> refers to the original object may be used but only in limited ways.
So there's a loophole in the standard for pointers and
references to partially constructed objects. Thanks.
Unfortunately, there's no explict way to say "this object
is partially constructed", or "construction is now complete",
which leads to unsound, even if marginally legal, code.
This is yet another reason why design by contract will
never work well in C++. Object semantics are insufficiently
sound.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Sat, 9 Oct 2004 17:28:57 GMT Raw View
John Nagle wrote:
...
> This is yet another reason why design by contract will
> never work well in C++. Object semantics are insufficiently
> sound.
Help me understand what you mean by this.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: nagle@animats.com (John Nagle)
Date: Mon, 11 Oct 2004 18:20:13 GMT Raw View
Gianni Mariani wrote:
> John Nagle wrote:
> ...
>
>> This is yet another reason why design by contract will
>> never work well in C++. Object semantics are insufficiently
>> sound.
>
>
> Help me understand what you mean by this.
OK.
If you want to have object function member entry and exit
conditions, and object invariants, you have to be very clear
about when they're supposed to be true. The general idea
is that object invariants must be true whenever control
is outside the object. They're then expected to be true
at the entry to any public member function.
Classic C++ problems in this area mostly involve object
reentrancy, where the issue of when control is "inside"
the object becomes ambiguous.
-- Calling a public member function from another member
function implies object reentrancy. The invariant has
to be true at entry to the member function, but since
invariants are normally checked when control leaves
an object, the invariant isn't necessarily in a true
state.
-- Calling a public member function indirectly (object
A calls object B which calls object A) creates the same
situation as above, but it's harder to detect. This
particular situation is a classic cause of GUI class
system bugs.
-- Calling a public member function from an constructor
results in a call to an object which is not yet valid.
This is formally incorrect.
-- Threads may cause object reentrancy. The interaction of
threads, locking, and design by contract is difficult.
The threading people usually don't consider design by contract,
and the design by contract people usually don't consider
threads.
-- "delete(this)" is still allowed. That violates any invariant
at exit.
All of these problems can be fixed, but they require tightening
up the language to do so. There's such hostility to tightening
up C++ that it's hopeless to even try, at least with the current
committee.
John Nagle
Animats
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: belvis@pacbell.net (Bob Bell)
Date: Thu, 14 Oct 2004 20:24:53 GMT Raw View
In article <q_lad.27681$QJ3.9776@newssvr21.news.prodigy.com>,
nagle@animats.com (John Nagle) wrote:
> Gianni Mariani wrote:
> > Help me understand what you mean by this.
>
> OK.
>
> If you want to have object function member entry and exit
> conditions, and object invariants, you have to be very clear
> about when they're supposed to be true. The general idea
> is that object invariants must be true whenever control
> is outside the object. They're then expected to be true
> at the entry to any public member function.
>
> Classic C++ problems in this area mostly involve object
> reentrancy, where the issue of when control is "inside"
> the object becomes ambiguous.
Perhaps the idea of "inside" and "outside" the object is flawed. It
seems to me to make more sense to think of being inside or outside a
function which must uphold invariants. As far as I can tell, when you
look at the problem this way, most of the issues below disappear.
There are clients who call public member functions and who cannot
establish the invariants of an object (because they cannot manipulate
the state of the object directly). Therefore, when a public member
function exits, it must check that the invariants are established.
At the entrance to a public member function, the member function must be
able to expect that the invariants are established; the invariants
represent the assumptions that the member function is allowed to make in
order to perform its work. Because the public member function can be
called from another member function of the same class, there is a chance
that the invariants don't hold. Therefore, when a public member function
is entered, it must check that the invariants are established.
In other words, the only thing we need to be concerned about is the
boundaries of functions which must uphold invariants. Trying to decide
when we are "inside" or "outside" of the object's control just
complicates things.
> -- Calling a public member function from another member
> function implies object reentrancy. The invariant has
> to be true at entry to the member function, but since
> invariants are normally checked when control leaves
> an object, the invariant isn't necessarily in a true
> state.
If an object's invariants must be true when a public member function is
called, then they must be true. A member function of an object that
breaks the invariants of the object before calling a public member
function is simply in error.
> -- Calling a public member function indirectly (object
> A calls object B which calls object A) creates the same
> situation as above, but it's harder to detect. This
> particular situation is a classic cause of GUI class
> system bugs.
Same issue. If A suspends its own invariants, then calls B which calls A
publicly again, there is an error.
> -- Calling a public member function from an constructor
> results in a call to an object which is not yet valid.
> This is formally incorrect.
Only if the invariants are not established by the time the constructor
calls the member function. This is under the control of the class
author. So again, if the public member function is called and the
invariants are not yet established, there is an error. If the invariants
are established, there is no problem.
> -- Threads may cause object reentrancy. The interaction of
> threads, locking, and design by contract is difficult.
> The threading people usually don't consider design by contract,
> and the design by contract people usually don't consider
> threads.
There is an issue here, but as far as I can see, it's the same issue
even if design by contract is not in the picture. For example, if I have
a member function that breaks invariants (even if they aren't checked,
as in today's C++) and I want to call that function reentrantly from
more than one thread, I have a problem. If we add automatic checking of
invariants, the problem remains the same, as do the available solutions.
> -- "delete(this)" is still allowed. That violates any invariant
> at exit.
This issue is the only one that isn't simplified by the "inside/outside
a member function" viewpoint as opposed to the "inside/outside an
object" viewpoint. The only way I can see accounting for this is to note
that design by contract would have to be optional (at the very least, it
would only be used in classes that expressed invariants, preconditions
or postconditions). By default, all public and protected member
functions would have invariants checked at entry and exit, and private
ones would not (perhaps it would even be possible to express different
sets of invariants for public, protected and private functions). It
should be possible for a programmer to override these defaults; in this
example, the programmer would disable invariant checks at the exit of
the function that included "delete this;".
A more serious problem with design by contract in C++ are opportunities
for the programmer to alter object state without invariants being
checked. For example:
void Foo::F(Foo& arg)
{
arg.mData = 10;
}
Here, the invariants for *this would be checked automatically; there is
no way in general to check the invariants of other Foo objects involved
in the execution of a member function. Friend functions have a similar
loophole. However, it may not be that big of a problem; presumably,
sometime in the future the objects in question will have public member
functions executed, and the invariant failures will be detected then.
Bob
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: news0@nospam.demon.co.uk (John G Harris)
Date: Thu, 14 Oct 2004 21:09:59 GMT Raw View
In message <q_lad.27681$QJ3.9776@newssvr21.news.prodigy.com>, John Nagle
<nagle@animats.com> writes
<snip>
> -- Calling a public member function from another member
> function implies object reentrancy. The invariant has
> to be true at entry to the member function, but since
> invariants are normally checked when control leaves
> an object, the invariant isn't necessarily in a true
> state.
>
> -- Calling a public member function indirectly (object
> A calls object B which calls object A) creates the same
> situation as above, but it's harder to detect. This
> particular situation is a classic cause of GUI class
> system bugs.
>
> -- Calling a public member function from an constructor
> results in a call to an object which is not yet valid.
> This is formally incorrect.
>
> -- Threads may cause object reentrancy. The interaction of
> threads, locking, and design by contract is difficult.
> The threading people usually don't consider design by contract,
> and the design by contract people usually don't consider
> threads.
>
> -- "delete(this)" is still allowed. That violates any invariant
> at exit.
>
>All of these problems can be fixed, but they require tightening
>up the language to do so. There's such hostility to tightening
>up C++ that it's hopeless to even try, at least with the current
>committee.
Your examples appear to assume that invariants and pre-conditions will
not tell the real truth. Your final conclusion appears to be saying that
telling the truth is unpopular and so should not be part of a
programming language. I find that rather sad.
John
--
John Harris
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]