c++ question regarding exception safety

U

unix.sh

In Section E.3.5. of the book 'the c++ programming language 3rd
edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
in the vector_base destrctor of the vector_base implmentation.

In my understanding, if this statement failed(v=alloc.allocate(n)) in
the contrstructor, the destrctor will never be called. So no check
needed in the destructor.

But according to the book, Bjarned says clearly in the book(p. 950,
Section E.3.5)

Note that this attempt to write safer code complicates the invariant
for the class: It is no longer
guaranteed that v points to allocated memory. Now v might be 0 .

I am confused now.

Thanks for your help,

Michael
 
A

Alf P. Steinbach

* (e-mail address removed):
In Section E.3.5. of the book 'the c++ programming language 3rd
edtion' by Bjarne Stroustrup, why it needs to add the check: 'if (v)'
in the vector_base destrctor of the vector_base implmentation.

In my understanding, if this statement failed(v=alloc.allocate(n)) in
the contrstructor, the destrctor will never be called. So no check
needed in the destructor.

But according to the book, Bjarned says clearly in the book(p. 950,
Section E.3.5)

Note that this attempt to write safer code complicates the invariant
for the class: It is no longer
guaranteed that v points to allocated memory. Now v might be 0 .

I am confused now.

As I recall, the point was that alloc.allocate(n) does /not/ throw an exception,
that that constructor does not signal failure by way of an exception but instead
notes in the object state (e.g. by having a null-pointer value somewhere) that
it has failed to initialize.

As Bjarne notes, that complicates the class invariant: it essentially introduces
a meta-level class invariant, "object has been initialized OR (real class
invariant)".

That's known as a zombie object, and they're a common problem in
garbage-collection based languages like Java and C# that do not have automated
deterministic destruction. Mostly this problem manifests as complicated
implementation code (peppered with checks for validity), equally complicated
client code (peppered with manual cleanup calls).


Cheers, & hth.,

- Alf
 
U

unix.sh

* (e-mail address removed):










As I recall, the point was that alloc.allocate(n) does /not/ throw an exception,
that that constructor does not signal failure by way of an exception but instead
notes in the object state (e.g. by having a null-pointer value somewhere) that
it has failed to initialize.

As Bjarne notes, that complicates the class invariant: it essentially introduces
a meta-level class invariant, "object has been initialized OR (real class
invariant)".

That's known as a zombie object, and they're a common problem in
garbage-collection based languages like Java and C# that do not have automated
deterministic destruction.  Mostly this problem manifests as complicated
implementation code (peppered with checks for validity), equally complicated
client code (peppered with manual cleanup calls).

Cheers, & hth.,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?- Hide quoted text -

- Show quoted text -

Thanks a lot for your information.

I understand the ponit Bjarne tries to express. But my question again
is what's the difference between the explicit assignment
v=alloc.allocate(n) and v(alloc.allocate(n)), the later
initiailization style is being used in the implementation in Section E.
3.2.(p. 943). According to the book, the later
one( v(alloc.alocate(n)) in the initializer list) is better than
v=alloc.allocate(n) in the constructor. In my understanding if
alloc.allocate(n) doesn't throw exception, v is still a dangling
pointer, which is the same as v=0. So both of them are the same.

Thanks,
Michael
 
A

Alf P. Steinbach

* (e-mail address removed):
Thanks a lot for your information.

I understand the ponit Bjarne tries to express. But my question again
is what's the difference between the explicit assignment
v=alloc.allocate(n) and v(alloc.allocate(n)), the later
initiailization style is being used in the implementation in Section E.
3.2.(p. 943). According to the book, the later
one( v(alloc.alocate(n)) in the initializer list) is better than
v=alloc.allocate(n) in the constructor. In my understanding if
alloc.allocate(n) doesn't throw exception, v is still a dangling
pointer, which is the same as v=0. So both of them are the same.

Thanks,
Michael

Please don't quote signatures.
 
J

James Kanze

* (e-mail address removed):

[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.

You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects. I would
have thought that zombie objects occured because of the lack of
exceptions. (Anything you can do with a destructor in C++, you
can do with a try block in Java. In client code, of course,
the try block is usually a lot more painful, but in a
constructor, I don't think it makes as much difference. And of
course, there's no relationship between "garbage collection
based" and "do not have automated deterministic destruction":
the current proposals are for C++ to have both, and of course,
there are an awful lot of older languages out there that have
neither.)
 
J

James Kanze

On Mar 6, 12:33 pm, "Alf P. Steinbach" <[email protected]> wrote:

[...]
I understand the ponit Bjarne tries to express. But my
question again is what's the difference between the explicit
assignment v=alloc.allocate(n) and v(alloc.allocate(n)), the
later initiailization style is being used in the
implementation in Section E. 3.2.(p. 943). According to the
book, the later one( v(alloc.alocate(n)) in the initializer
list) is better than v=alloc.allocate(n) in the constructor.
In my understanding if alloc.allocate(n) doesn't throw
exception, v is still a dangling pointer, which is the same as
v=0. So both of them are the same.

It depends more on the type than on explicit initialization or
not. The point is that once v has been constructed, its
destructor will be called if the constructor of the containing
class exits via an exception. If v is a non-class type (e.g. a
raw pointer), the destructor is a no-op, and nothing happens.
If v is a class type, it could do some clean-up.
 
A

Alf P. Steinbach

* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.

You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.

You have known that earlier... ;-)

When you don't have automatic deterministic destruction, and you have some
resource to release, you have to implement some kind of manual cleanup, e.g. a
member function destroy() available to client code.

There's no way to guarantee that destroy() is called exactly once or only when
the object will no longer be used. Worse, to support code that forgets to call
destroy(), it's not uncommon to also do cleanup in a finalize() function that
may be called by the garbage collection. And a common solution is to let the
cleanup code note in the object's state that it's destroyed, a zombie.

An example of this monstrosity (googled this up now just for your convenience):
<url: http://www.javapractices.com/topic/TopicAction.do?Id=43>.

Problem with that example: abstracting a resource that can become invalid on its
own, such as a file or db connection, is inherently difficult, so that the
complexity in that zombie solution doesn't quite properly illustrate the evils
of zombies. So think about e.g. a window handle instead of a db handle. With
RAII, however, the checking of zombieness can be automated and centralized by a
smart pointer instead of having zombie checks peppered throughout the code.


Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.
You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.
You have known that earlier... ;-)
When you don't have automatic deterministic destruction, and
you have some resource to release, you have to implement some
kind of manual cleanup, e.g. a member function destroy()
available to client code.

I think we're talking about a different context. I don't see
the relevance with regards to constructors or zombie objects
here. (In practice, the destroy() function in C++ is the
destructor. But that's not the issue here.)
There's no way to guarantee that destroy() is called exactly
once or only when the object will no longer be used. Worse, to
support code that forgets to call destroy(), it's not uncommon
to also do cleanup in a finalize() function that may be called
by the garbage collection. And a common solution is to let
the cleanup code note in the object's state that it's
destroyed, a zombie.

Now you've lost me completely. I can't make heads or tails out
of the above---it seems to mix any number of separate concepts.
An example of this monstrosity (googled this up now just for
your convenience):
<url:http://www.javapractices.com/topic/TopicAction.do?Id=43>.
Problem with that example: abstracting a resource that can
become invalid on its own, such as a file or db connection, is
inherently difficult, so that the complexity in that zombie
solution doesn't quite properly illustrate the evils of
zombies.

OK. We agree there. But my impression is that the problem
being addressed in all this is that Java doesn't have
destructors, so you need a finally block. And as I said, within
the constructor, that's less of a problem than elsewhere,
because it's within the class code itself---it doesn't introduce
a constraint on user code. (Thus, my pre-standard array classes
uses a try block to handle the case where a copy constructor
threw, rather than some special extra class with a destructor.)

Note that in both Java and C++, you can have dangling pointers.
With the difference that you can reliably detect when they are
used in Java (or in C++ with garbage collection). (Except, of
course, I've seen damded little Java code that actually tries to
detect them. Detecting them is the sort of reliability question
that doesn't seem to interest Java programmers.) But that,
again, is more or less separate from the problem of cleaning up
after an error in the constructor.
So think about e.g. a window handle instead of a db handle.
With RAII, however, the checking of zombieness can be
automated and centralized by a smart pointer instead of having
zombie checks peppered throughout the code.

That is, again, a different issue. Although if I understand
your suggestion correctly, it's an interesting idea---using
normal C++ semantics for destruction, but using smart pointers
for memory management. (I'd buy into it more if smart pointers
could handle cycles easily.)
 
A

Alf P. Steinbach

* James Kanze:
* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.
You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.
You have known that earlier... ;-)
When you don't have automatic deterministic destruction, and
you have some resource to release, you have to implement some
kind of manual cleanup, e.g. a member function destroy()
available to client code.

I think we're talking about a different context. I don't see
the relevance with regards to constructors or zombie objects
here.

See below, or earlier in the thread, keeping in mind that a constructor's main
job is (usually) to establish the class invariant.

Or perhaps read Bjarne's article that the OP was referring to.

This all hangs together.

(In practice, the destroy() function in C++ is the
destructor. But that's not the issue here.)


Now you've lost me completely. I can't make heads or tails out
of the above---it seems to mix any number of separate concepts.

Yes, this all hangs together.

OK. We agree there. But my impression is that the problem
being addressed in all this is that Java doesn't have
destructors, so you need a finally block.

That's one problem that can lead to zombies.


Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:
* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.
You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.
You have known that earlier... ;-)
When you don't have automatic deterministic destruction, and
you have some resource to release, you have to implement some
kind of manual cleanup, e.g. a member function destroy()
available to client code.
I think we're talking about a different context. I don't see
the relevance with regards to constructors or zombie objects
here.
See below, or earlier in the thread, keeping in mind that a
constructor's main job is (usually) to establish the class
invariant.

Right. The constructor's. Deterministic destruction and
garbage collection aren't really relevant here: the question is
what happens when for some reason the the constructor cannot
establish the invariants. If the object "exists" anyway (or
perhaps more correctly, in C++ standardese, if there is an
lvalue expression which can refer to it), then you have a
zombie. Zombies can easily be objects with no destructor
(although I suspect that the case is rare in standard C++), and
Zombieism affects local objects (on stack) just like any others;
dynamic allocation is not necessary for zombies to exist.

In older C++, before exceptions were introduced, zombies
couldn't be avoided. If you wrote something like:

{
C obj ;
// ...
}

there was no way to avoid obj "existing". So you had to create
a zombie state, require the client code to check it, verify it
on each access, etc. The key to not having zombies is
exceptions (and you can avoid them just as well in Java as in
C++).
Or perhaps read Bjarne's article that the OP was referring to.

Bjarne's article didn't really mention zombies. It talked about
cleaning up in the destructor, before (or as a result of)
throwing the exception. If I understood it correctly, Bjarne
didn't consider the possibility of having a zombie; he only
talked about what happens when reporting an error via an
exception.

Again, the problem is common to all languages which have
exceptions. The tools available to solve the problem do vary
from one language to the next---in Java, you must use a try
block; in C++, you have the choice between a try block and a
subobject with a destructor, which will be called once the
subobject has been fully constructed.

Having a choice is, of course, a good thing. But the choice the
programmer makes isn't that critical here---either way, he's
cleaned up. (I'm talking here about explicitly creating a class
which has no other reason d'être but to exploit this behavior of
destructors. Obviously, if the class you are writing is a
client of another, existing class, then that class should take
care of all necessary clean-up in its destructor, and the fact
that you, as a client, don't have to write explicit try/catch
blocks is a definite advantage.)
This all hangs together.

I still don't see any real relationship. Zombie objects are a
result of design choices (or in pre-exception days, the lack of
a possible choice) concerning the constructor. Deterministic
destruction solves a different set of problems, and garbage
collection is yet a third issue, orthogonal to the other two.
Yes, this all hangs together.

How? Before C++ had exceptions, zombie classes were a fact of
life. Even when no dynamic allocation was involved, and in a
few odd cases, when no resources at all were involved.
That's one problem that can lead to zombies.

How? This is one case where Java and C++ behave almost
identically:

{
MyClass obj = new MyClass ;
// ...
}

in Java behaves exactly like:

{
MyClass obj ;
// ...
}

in C++. If the constructor exits via an exception, there is no
way of accessing the non-existing (or invalid) object. Thus, no
zombie.
 
A

Alf P. Steinbach

* James Kanze:
* James Kanze:
* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.
You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.
You have known that earlier... ;-)
When you don't have automatic deterministic destruction, and
you have some resource to release, you have to implement some
kind of manual cleanup, e.g. a member function destroy()
available to client code.
I think we're talking about a different context. I don't see
the relevance with regards to constructors or zombie objects
here.
See below, or earlier in the thread, keeping in mind that a
constructor's main job is (usually) to establish the class
invariant.

Right. The constructor's. Deterministic destruction and
garbage collection aren't really relevant here

Trying to understand the relevance by maintaining there isn't one, isn't exactly
the forward way of going about it... :)

For that matter, trying to understand the connections between A, B and C by
discussing each in isolation, is not the most fruitful way to proceed.

It seems that it might help if again I mention class invariants.

Let's focus on class invariants.

Questions that might help you:

* What is the connection between constructors and class invariants?

* What is the connection between non-deterministic destruction and class
invariants?

It is perhaps this latter question that is problematic for you.

A bad approach for understanding is to start by denying there is any connection.

A good approach might be to study the Java example I linked to. And I mean
really study it. For example, try to answer these questions:

* What is the class invariant in that example?

* Exactly how does the lack of deterministic destruction, in that example,
influence the choice of class invariant?

Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:
* James Kanze:
* James Kanze:
* (e-mail address removed):
[...]
That's known as a zombie object, and they're a common problem
in garbage-collection based languages like Java and C# that do
not have automated deterministic destruction.
You've got me there. How does the lack of automated
deterministic destruction relate to zombie objects.
You have known that earlier... ;-)
When you don't have automatic deterministic destruction, and
you have some resource to release, you have to implement some
kind of manual cleanup, e.g. a member function destroy()
available to client code.
I think we're talking about a different context. I don't see
the relevance with regards to constructors or zombie objects
here.
See below, or earlier in the thread, keeping in mind that a
constructor's main job is (usually) to establish the class
invariant.
Right. The constructor's. Deterministic destruction and
garbage collection aren't really relevant here
Trying to understand the relevance by maintaining there isn't
one, isn't exactly the forward way of going about it... :)

Ok, but... You can have zombies which don't allocate any
resources, and don't need destructors. By definition, the
zombie state is a result of construction. (Or maybe we're using
different definitions.) You can just as easily have zombies in
Java as in C++, and the way to avoid them is exactly the same in
both languages. although their situation with regards to
deterministic destructors and garbage collection is exactly
opposite. You could not effectively avoid them in the C++ I
first learned, before exceptions. Although that C++ was in the
same position as modern C++ with regards to deterministic
destructors and garbage collection. So I'm having a very
difficult time seeing the relationship.
For that matter, trying to understand the connections between
A, B and C by discussing each in isolation, is not the most
fruitful way to proceed.

True but... The situation here is the same in modern Java and
modern C++, which differ greatly in both B and C, and it is
radically different in earlier C++ and modern C++, in which B
and C were identical.
It seems that it might help if again I mention class
invariants.
Let's focus on class invariants.

So far, we're in total agreement. The definition I generally
use for a zombie is an object whose constructor could not
establish the invariants, and yet in some way "exists". (More
correctly, one might say that the object doesn't exist, but that
lvalue expressions which designate it are still possible.)
Questions that might help you:
* What is the connection between constructors and class invariants?
* What is the connection between non-deterministic
destruction and class invariants?
It is perhaps this latter question that is problematic for you.

Exactly. About the only relationship I can find between
destructors and invariants is that it is often necessary for the
invariants to hold in order to call the destructor.
A bad approach for understanding is to start by denying there
is any connection.

If you put it that way. All I can say is that my reaction is
based on my experience with older C++, modern C++ and Java. The
first had zombies, the other two don't (when written
correctly---and probably except for some special cases).
A good approach might be to study the Java example I linked
to. And I mean really study it. For example, try to answer
these questions:
* What is the class invariant in that example?
* Exactly how does the lack of deterministic destruction, in
that example, influence the choice of class invariant?

OK. But the first thing I see is:

public DbConnection () {
//build a connection and assign it to a field
//elided.. fConnection =
ConnectionPool.getInstance().getConnection();
}

That's the destructor, and the interesting part---the essential
part, in fact, has been elided. If the constructor throws an
exception if it cannot establish the invariants, then there can
be no zombie.

What can happen, but in this regard, Java is not really
different from C++, is that you can have dangling pointers to
the object, i.e. you can have a pointer to the object after it
has been destroy. But a dangling pointer isn't a zombie, even
in Java. (And in this case, garbage collection ensures that you
can detect the error in Java.)

The other thing that can happen in Java is that it's easier to
misuse the class, because you don't have deterministic
destructors. There's no disagreement on that
point---deterministic destructors are very useful in a certain
number of frequent scenarios (where object lifetime corresponds
to automatic lifetime---otherwise, we're back to no real
difference between Java and C++). But again, it's not a
question of zombies: C++ makes the use of this class
significantly easier, but if you use it correctly, it works the
same in both cases: in Java, try/finally blocks are the
equivalent of deterministic destructors in C++. With the
difference that when you combine deterministic destruction AND
on stack objects (which Java doesn't have either), you reduce
coupling significantly, since the client code doesn't have to do
anything (and thus, can't forget doing anything). All of which
is (I hope) well known---the people working on C# and CLI
certainly recognized that this was one place where C++ was
better than Java, regardless of what they thought about the
rest.

One final comment on the code, of course---it shows up another
weakness of Java, and one that isn't generally recognized as
such by Java programmers: the absense of an assert which stops
everything. In any really robust code, you'd want an assert in
the finalization method: if the client code didn't conform to
the contract, it's broken, and you can't count on the program
containing it. (But as I've said elsewhere, I think, Java isn't
designed for writing really robust code.)
 
A

Alf P. Steinbach

* James Kanze:
A good approach might be to study the Java example I linked
to. And I mean really study it. For example, try to answer
these questions:
* What is the class invariant in that example?
* Exactly how does the lack of deterministic destruction, in
that example, influence the choice of class invariant?

OK. But the first thing I see is:

public DbConnection () {
//build a connection and assign it to a field
//elided.. fConnection =
ConnectionPool.getInstance().getConnection();
}

That's the [constructor], and the interesting part---the essential
part, in fact, has been elided. If the constructor throws an
exception if it cannot establish the invariants, then there can
be no zombie.

The class invariant may be a bit easier to see by examining the following (Java)
code snippet from the article:

public void destroy() throws SQLException {
if (fIsDestroyed) {
return;
}
else{
if (fConnection != null) fConnection.close();
fConnection = null;
//flag that destory has been called, and that
//no further calls on this object are valid
fIsDestroyed = true;
}
}

Implied by that code:

boolean invariantHolds()
{
return
fIsDestroyed ||
(fConnection == null || isValidConnection( fConnection ));
}

The possibility of (fConnection == null) just complicates the picture. It seems
to be due to the author not being sure whether getConnection() signals failure
by returning null or throwing an exception. Another possibility might be that
the author envisions some closeConnection() method in addition to destroy().

If we assume that getConnection() throws on failure, and there's no additional
closeConnection() method, then things can become more clear.

For in that case fConnection can't be null and the class invariant reduces to

boolean invariantHolds()
{
return
fIsDestroyed || isValidConnection( fConnection );
}

which can be rewritten, for clarity, as

boolean isZombie() { return fIsDestroyed; }

boolean nonZombieInvariantHolds() { return isValidConnection( fConnection ); }

boolean invariantHolds() { return isZombie() || nonZombieInvariantHolds(); }

I hope you're with me so far in this analysis, because there's no point going
further without agreeing on the above conclusion. Namely, that we have a
constructor that signals failure (not able to establish class invariant) by
throwing, that we have something that can reasonably be called a class invariant
that holds for any constructed object (I find it more clear to refer to that
something as a /meta/ class invariant, and reserve plain "class invariant" for
what the function nonZombieInvariantHolds() checks), and yet we have a zombie.
Point of possible contention: where fIsDestroyed might be set to true, and why.

Hint: it's not in the constructor.

In passing, yes, lack of exception support is also a common cause of zombies.
It can be worked around by always allocating objects dynamically, or via dynamic
allocation-like syntax. Which is a heavy price to pay in C++.


Cheers, & hth.,

- Alf
 
J

James Kanze

* James Kanze:
A good approach might be to study the Java example I linked
to. And I mean really study it. For example, try to answer
these questions:
* What is the class invariant in that example?
* Exactly how does the lack of deterministic destruction, in
that example, influence the choice of class invariant?
OK. But the first thing I see is:
public DbConnection () {
//build a connection and assign it to a field
//elided.. fConnection =
ConnectionPool.getInstance().getConnection();
}
That's the [constructor], and the interesting part---the essential
part, in fact, has been elided. If the constructor throws an
exception if it cannot establish the invariants, then there can
be no zombie.
The class invariant may be a bit easier to see by examining
the following (Java) code snippet from the article:
public void destroy() throws SQLException {
if (fIsDestroyed) {
return;
}
else{
if (fConnection != null) fConnection.close();
fConnection = null;
//flag that destory has been called, and that
//no further calls on this object are valid
fIsDestroyed = true;
}
}

Which doesn't really say anything about the class invarient.
But that's not really the point, is it. We can guess about the
class invariant, but the real question in relationship to
zombies is what the constructor does if it cannot establish it.
If the class invariant is that the class maintains an open
connection (which is probably not an acceptable class invariant
in this particular case, since it can become invalidated during
the life of the object, even if the client code rigorously
respects the contract---but for purposes of demonstration, I'll
accept it), then what does the constructor do if it cannot
establish this invariant.
Implied by that code:
boolean invariantHolds()
{
return
fIsDestroyed ||
(fConnection == null || isValidConnection( fConnection ));
}
The possibility of (fConnection == null) just complicates the
picture.

Agreed. I'd probably have merged this and the bool
fIsDestroyed. Again, however, without seeing the constructor:
if the invariant doesn't allow it, then the constructor should
throw if it can't create a valid connection. If the invariant
does allow for it, then you have to take it into consideration.

Personally, in the context of this example (i.e. ignoring the
fact that the connection can become invalidated during the
lifetime of the object), I'd consider the class invariant:
! fIsDestroyed
&& fConnection != NULL
&& isValidConnection( fConnection )
Of course, fIsDestroyed is just a debug device, and isn't
conceptually part of the object to begin with. And in C++, you
might elimate the pointer, and make the connection a member
object. In which case, the real invariant is just
isValidConnection( myConnection )
It seems to be due to the author not being sure whether
getConnection() signals failure by returning null or throwing
an exception.

It seems due to the fact that the author hasn't really decided
what his invariants are, and so doesn't know whether to treat
something as an invariant error (i.e. a fatal software
error---something which would trigger an assertion failure in
C++), or as a normal state of the object. Until we know this,
we can't really talk much about whether we might have a zombie
or not.

What is certain, of course, is that if the author decides that
having a valid connection is part of the class invariant, in
Java and in modern C++, he can terminate the constructor with an
exception, and the client code can never access an object which
doesn't meet the invariant.
Another possibility might be that the author envisions some
closeConnection() method in addition to destroy().

So you're coming around to my point of view: the author has
withheld critical information from us---information we need to
really discuss the issue further.

Bad design is bad design. Not defining the exact class
invariants before writing a single line of code is bad design.
(In this case, of course, the author "elided" many things, so we
don't know whether this is bad design, or simple elided
information.)
If we assume that getConnection() throws on failure, and
there's no additional closeConnection() method, then things
can become more clear.

OK. For purposes of demonstration, I'll accept the idea that
"has a valid connection" is part of the class invariant (if
you'll accept to pretend that the connection can't become
invalid prematurely---we're creating a somewhat artificial
example for purposes of demonstration; I think we both agree
that in real life, this particular case would present a some
additional complications which we are sweeping under the rug).
For in that case fConnection can't be null and the class
invariant reduces to
boolean invariantHolds()
{
return
fIsDestroyed || isValidConnection( fConnection );
}
which can be rewritten, for clarity, as
boolean isZombie() { return fIsDestroyed; }
boolean nonZombieInvariantHolds() { return isValidConnection( fConnection ); }
boolean invariantHolds() { return isZombie() || nonZombieInvariantHolds(); }
I hope you're with me so far in this analysis,

The problem here is that you've slipped the term "zombie" in
with no explination.

I somewhat suspected that part of our problem might be with
definitions, rather than the underlying principles. My
interpretation of the "demo" program was that the intent was for
the destroy() function to terminate object lifetime. In which
case, all of the logic around fIsDestroyed is debug logic; *if*
the client code conforms to the contract, then it will never
call a member function with fIsDestroyed false, and when
terminate() is called by the system, fIsDestroyed will be true.
IMHO, the correct way of handling such debug code is with
assert(), i.e. in case of an error, you bring the system down
(because you no longer have confidence in the program). Java
doesn't support such, however, so you do what you can.

Note that in that case, of course, your "isZombie()" function
above always returns false.

IMHO, this is an important distinction. When I speak of a
zombie, it's something which may occur even when the client code
is correct, and conforms to the contract. For example, if the
constructor of this object doesn't throw if it cannot establish
the connection. It's a state correct client code has to deal
with (with the emphesis on *correct*).

What you seem to be getting at is something I've always called a
dangling pointer. Using a dangling pointer is an error in
client code.

I think that there are actually three distinct issues involved
here, and I find it clearer to give each a separate name:

-- If the constructor is unable to establish the invariant, but
still leaves an accessible object, then we have a zombie.
The solution to this is exceptions.

-- If the lifetime of the object ends (regardless of how, for
the moment), but the object is still accessible, then we
have a dangling pointer (or lvalue expression---but in
practice, the problem will only occur with pointers or
references). The solution to this is "don't do it".
Seriously, the solution is that all concerned parties must
be notified---there is no general solution in either
language (although some types of smart pointers may help in
specific cases).

Note that Java and C++ are exactly the same in this regard
(although some Java advocates like to pretend that dangling
pointers can't exist in the language). With the one proviso
that you *can* implement serious runtime checking for this
in Java, but not in C++ (not even in C++ with garbage
collection, since the dangling pointer can be to an object
with automatic lifetime). It's a weak proviso, however,
because in practice, Java programmers don't implement such
checking, and Java doesn't provide anything you can
reasonably do (e.g. like abort()) if you detect the error.

-- Some objects have very deterministic lifetimes, which must
be terminated at a very specific instant (or as soon as
possible---but I find the "very specific instant" to be more
prevelent in my code). C++ provides an "official" language
mechanism for this: the destructor---even better, when you
can arrange for this deterministic lifetime to correspond to
a scope, C++ will call the destructor automatically for
you---you don't have to depend on the client code not
forgetting. In Java, all you have is an ad hoc mechanism,
and it's up to the client code to conform to the contract;
in the case where lifetime corresponds to a scope, Java does
have try/finally, which simplifies somewhat the client code,
but it is still far from the convenience of C++-like
destructors. When the lifetime doesn't correspond to an
automatic scope, of course, you must terminate it explicitly
in both languages (although C++ has the slight advantage of
having a language sanctified "official" syntax for this; in
Java, you never know whether you have to call dispose(), or
destroy(), or what).

In practice, if you go back some years, you'll find that
most of the times an object needed automatic lifetime, it
was only for memory management or for handling locking.
Java added language based mechanisms to handle those. As
we've evolved using C++, however, we (or at least I) have
found that the basic principle can be very useful in a lot
of other cases---I probably use it more often for
transaction management (in the largest sense) than for
either of the original uses. (But then, I use garbage
collection---otherwise, I suspect that memory management
would still predominate. And of course, I also use it for
handling locks, but those uses are generally isolated in a
very few higher level mechanisms, like a message queue.
Whereas I use transaction semantics a lot---an object which
"undoes" everything if the function "commit()" hasn't been
called on it before the destructor is called.)

Anyhow, three separate issues, with three different names.
because there's no point going further without agreeing on the
above conclusion. Namely, that we have a constructor that
signals failure (not able to establish class invariant) by
throwing, that we have something that can reasonably be called
a class invariant that holds for any constructed object (I
find it more clear to refer to that something as a /meta/
class invariant, and reserve plain "class invariant" for what
the function nonZombieInvariantHolds() checks), and yet we
have a zombie.

Would you also call it a zombie in C++ is someone did:

DbConnection* p = new DbConnection(...) ;
// ...
delete p ;
p->...

If so, then I think we'll just have to agree to disagree on the
terminology. If not, what's the difference between this, and:

DbConnection p = new DbConnection( ... ) ;
// ...
p.destroy() ;
p. ...

in Java? The only difference I see is syntax.
Point of possible contention: where
fIsDestroyed might be set to true, and why.
Hint: it's not in the constructor.

The real point of contention is whether fIsDestroyed is
conceptually part of the object state, or whether it is simply
debugging code. IMHO, after p.destroy(), you don't have a
zombie object, you have a dangling pointer. I think you're
being mislead by the fact that Java doesn't have direct language
support for managing object lifetime, and C++ does. Where as I
don't see that as being a real difference---objects have
lifetimes, and some objects have very deterministic lifetimes,
regardless of what the language says about them. And a pointer
or a reference to an object which is no longer alive is a
dangling pointer---regardless of whether the memory which once
held that object is available for re-allocation or not.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top