From: Alf P. Steinbach on
[Addressing just two basic points]

* David Abrahams -> Alf P. Steinbach:
>
> > and third, because there's no (under assumption of valid higher
> > level states) guarantee of execution, as there is with RAII.
>
> Not sure what you had in mind here.

With exceptions the client code that needs cleanup doesn't need to take
any explicit extra action to have that cleanup performed. That's one less
thing that can go wrong in the initial coding, and that's one main reason
why exceptions are used for less fatal circumstances. For example, if you
create a huge temporary file (as someone else recently used as an example in
this thread), then you can guard against exceptions by using an automatic
object with a suitable destructor,

TempFile f(aLargeSize);

and when the code is all exception based that simple declaration is all
that's needed.

However when cleanup for a fatal error is centralized (to be run at the
detection call-chain level) you have to adapt the TempFile class to support
that centralized clean-up, or do something extra each place a TempFile is
used, to hook it up with the clean-up, or not clean up.

Ideally the destructor should be able to differentiate between an ordinary
exception (where destruction failure could be handled by throwing a "hard"
exception) and a "hard" exception (where it's known the process is going to
terminate anyway, no special action except logging needed on failure). But
as it is a C++ destructor doesn't even know whether it's invoked via
exception-induced stack unwinding or not. As with most other high-level
concepts, e.g. modules, where we have to use the preprocessor, the language
support isn't there, and the concept must be emulated via conventions.


> > And, it's reasonable and common (it even has built-in language support)
> > to do recursive clean-up when an object is destroyed.
>
> What does an object being destroyed have to do with anything?
>
> > But ensuring destruction of objects can be difficult when the stack
> > is not unwound. So with no stack unwinding the objects may have to
> > be designed to be able to do total clean-up without being destroyed,
> > which effectively means adding zombie states.
>
> No, you're going to stop the program anyway, which means the objects
> die automatically.

There's no concept of object death in the standard. ;-)

Assuming that you mean, those C++ objects are going to disappear: no more
process, no more objects. Yes, and that disappearance nearly without any
trace, leaving behind a mess and little or no information about the detailed
reasons, is what we're trying to avoid.

C++ object destruction is more than internal-to-the-process object
disappearance. C++ object destruction is recursive, and often involves
external resources. To invoke that recursive destruction with cleaning up
of external resources and possibly logging of states, etc., the objects have
to be destroyed via the C++ mechanisms, not just disappeared along with the
process.

But ensuring C++ destruction of objects can be difficult when the stack
is not unwound.

So with no stack unwinding the objects may have to be designed to be able to
do total clean-up without being destroyed, which effectively means adding
zombie states. The TempFile object above is a bad example in this respect
because a file object typically has a possible zombie state anyway, at least
if it's designed to deal with errors. But replace "file" with anything.

--
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?

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Dave Harris on
gerhard.menzl(a)hotmail.com (Gerhard Menzl) wrote (abridged):
> When flight control software encounters a negative altitude value,
> it had better shut down (and, hopefully, let the backup system
> take over). On the other hand, a word processor that aborts and
> destroys tons of unsaved work just because the spellchecker has met a
> violated invariant is just inacceptable.

I think your real problem there is that you have tons of unsaved work. You
can cope with program bugs, maybe, but you probably can't cope with O/S
crashes or power cuts so you need a more general strategy.

My employers write word processors. Our approach is to write the document
to a temporary file every 10 or so minutes. We delete it during a normal
shutdown and also after a successful user-save. Any documents found during
start-up are then unsaved work lost due to a crash, and we ask the user if
she wants them restored.

This mitigates the problem. They never lose more than 10 minutes work.
That's acceptable because crashes should be rare (if they are a daily
occurance we are already in big trouble). We are confident we can restore
the document because we had a known sane state when it was written out.

-- Dave Harris, Nottingham, UK.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: David Abrahams on
alfps(a)start.no (Alf P. Steinbach) writes:

> [Addressing just two basic points]
>
> * David Abrahams -> Alf P. Steinbach:
>>
>> > and third, because there's no (under assumption of valid higher
>> > level states) guarantee of execution, as there is with RAII.
>>
>> Not sure what you had in mind here.
>
> With exceptions the client code that needs cleanup doesn't need to take
> any explicit extra action to have that cleanup performed. That's one less
> thing that can go wrong in the initial coding, and that's one main reason
> why exceptions are used for less fatal circumstances.

That's true for things that need to be cleaned up "anyway" and are
allocated on the stack or managed by stack objects, but that leaves
out a lot. For example, your argument doesn't work for the particular
case you cited: interactive document editing software. The document
is not going to be "cleaned up" in the normal course of events.

> For example, if you create a huge temporary file (as someone else
> recently used as an example in this thread), then you can guard
> against exceptions by using an automatic object with a suitable
> destructor,
>
> TempFile f(aLargeSize);
>
> and when the code is all exception based that simple declaration is all
> that's needed.

That's true, but in my opinion temporary files fall into a separate
class of resource -- those that need to be cleaned up even in case of
fatal errors -- and that class should be dealt with by a separate
mechanism. If you are going to try to do something reasonable in the
face of programming errors, you probably need to deal with the case
where an exception specification is violated, or where an exception
gets thrown from a destructor during unwinding, anyway. So these
kinds of cleanups should be registered with atexit (and unregistered
when the corresponding stack object is destroyed) or in the
terminate() handler[1].

> However when cleanup for a fatal error is centralized (to be run at the
> detection call-chain level)

I still don't understand what you mean by "centralized."

> you have to adapt the TempFile class to support that centralized
> clean-up, or do something extra each place a TempFile is used, to
> hook it up with the clean-up, or not clean up.

Yep, exactly.

> Ideally the destructor should be able to differentiate between an ordinary
> exception (where destruction failure could be handled by throwing a "hard"
> exception) and a "hard" exception (where it's known the process is going to
> terminate anyway, no special action except logging needed on failure). But
> as it is a C++ destructor doesn't even know whether it's invoked via
> exception-induced stack unwinding or not.

Actually it does. That's what uncaught_exception() tells you. The
only problem with uncaught_exception is that it doesn't tell you when
you're in a

catch(...) { ... throw; }

block[2], but it does tell the truth: no stack unwinding is in progress.

> As with most other high-level concepts, e.g. modules, where we have
> to use the preprocessor,

Whoa, preprocessor??! How is that relevant here?

> the language support isn't there, and the concept must be emulated
> via conventions.

You can easily support this with a simple library (in fact I've been
meaning to write one for this purpose for years). I wouldn't call
using a library "emulation via convention."

>> > And, it's reasonable and common (it even has built-in language support)
>> > to do recursive clean-up when an object is destroyed.
>>
>> What does an object being destroyed have to do with anything?

??

>> > But ensuring destruction of objects can be difficult when the stack
>> > is not unwound. So with no stack unwinding the objects may have to
>> > be designed to be able to do total clean-up without being destroyed,
>> > which effectively means adding zombie states.
>>
>> No, you're going to stop the program anyway, which means the objects
>> die automatically.
>
> There's no concept of object death in the standard. ;-)
>
> Assuming that you mean, those C++ objects are going to disappear: no more
> process, no more objects.

There's no concept of "process" in the standard ;-)
Can we just speak English here? Yes, that's what I mean.

> Yes, and that disappearance nearly without any trace, leaving behind
> a mess

Only a small fraction of objects on the stack in a typical program are
responsible for any mess that might be left.

> and little or no information about the detailed reasons,
> is what we're trying to avoid.

The detailed reasons generally have nothing to do with most of what
was on the stack, and you're not going to instrument all of your
classes to dump their state on destruction anyway.

> C++ object destruction is more than internal-to-the-process object
> disappearance. C++ object destruction is recursive, and often involves
> external resources. To invoke that recursive destruction with cleaning up
> of external resources and possibly logging of states, etc.,

Ah, but you're not going to log unconditionally, are you? You don't
want to dump reams of log information for the normal control flow
case! So you need global state, too, if only to decide whether to log
something.

> the objects have to be destroyed via the C++ mechanisms, not just
> disappeared along with the process.

No, they clearly don't. We can see that because many successful C++
programs exit without falling off the end of main(), some even in the
normal case. Only _crucial_ cleanups need to be performed when
aborting due to a broken invariant.

> But ensuring C++ destruction of objects can be difficult when the
> stack is not unwound.
>
> So with no stack unwinding the objects may have to be designed to be able to
> do total clean-up without being destroyed, which effectively means adding
> zombie states.

I know what you mean, but in practice it doesn't matter especially
because at that point we are on a very direct train to termination.

> The TempFile object above is a bad example in this respect because a
> file object typically has a possible zombie state anyway, at least
> if it's designed to deal with errors. But replace "file" with
> anything.

"Balloon?"

Sorry, I can't imagine a crucial resource for which a manager object
should not have an "empty" state.

Footnotes:
[1] I'm being a little fuzzy about exactly
where because I'd need to consult the standard to make sure I got
everything right

[2] We can argue later about whether those blocks are a good idea.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Gerhard Menzl on
Peter Dimov wrote:

> The two options aren't "abort and destroy hours of work" and "throw an
> exception". The two options are "throw an exception" and "don't throw
> an exception".
>
> In particular, nothing prevents the failed assertion handler to
> attempt an emergency save, using a different file name (to not clobber
> the "last known good" save), a different file format (if the native
> format consists of a dump of the data structures and will likely
> produce an unreadable file), and a different, extra-paranoid code
> path. _Then_ abort.

How can a global handler obtain the necessary context information to
perform the emergency save?

> Yes, I tried to gave an example of that in the other post.
> Unfortunately, two important global invariants are "the heap is not
> corrupted" and "there are no dangling pointers that are causing
> damage", and a violation of those is usually manifested as a violation
> of another (possibly local) invariant.

I agree that in case of a corrupted heap (or stack), there is no point
in continuing. But there are many local invariants that do not affect
the global program state and thus hardly warrant an abort. How would you
handle those?

> A broken database connection, a corrupted table, a corrupted data file
> are not logic errors. The number of logic errors in a program is
> constant and does not depend on external factors. (Unless the program
> itself changes, i.e. you consider the information in the database
> "code", a part of the program.)

Sort of. A considerable part of the program logic rests in stored
procedures in the database itself.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Gerhard Menzl on
David Abrahams wrote:


> You seem to assume that aborting is the only alternative to unwinding
> when a violated invariant is detected. In an interactive application
> like a word processor it's usually possible to recover the state of
> the document and offer the user an opportunity to save when a violated
> invariant is detected. All of that can be done without any
> unwinding.

No, I was referring to an earlier posting of yours where you wrote:

> If preconditions are broken, your program state is broken, by
> definition. Trying to recover is generally ill-advised.

and

> Stop execution so I can debug the program. Good!

My understanding is that recovery without unwinding requires a separate,
handcrafted mechanism. Otherwise, how would you obtain the necessary
context that is normally not accessible in a global, low-level function
like an assertion handler?

You argued (elsewhere) that the Undo feature in a word processor
provides such a mechanism (or a substantial building block of one)
anyway. Well, what about applications that don't have an Undo feature?

> In that case, a corrupted database table is by definition *not* a
> broken precondition. IIUC, you are expected to write software that is
> guaranteed to work whether the database table is corrupt or not, and
> you seem to accept that challenge. Great! It's similar to writing
> software that is robust in the face of invalid user input. If the
> user's input is invalid, well, there's some functionality they can't
> get to until the input is corrected. Nobody I know would consider
> valid user input a precondition in that case.

I would not compare user input, which is always unpredictable, with the
state of a database. If, as in my case, a database table is accessed
exclusively by my application, which reads in data that have been
written by itself during earlier sessions, corrupt data are closer to a
broken invariant (or precondition) than to invalid user input.

> In your application, calling database table integrity a precondition
> is only going to confuse things and make your code more complicated.
> Once you understand that database integrity is not a precondition, it
> becomes very clear that you need to check for corruption in certain
> places and make sure that you do something sensible if you detect it.

I think it depends on whether you regard the database as an integral
part of your system or something external. In a sense, it is both. An
error caused by someone tinkering manually with the database, or by a
broken network connection is external, but an error caused by your own
application writing garbage that will lead to confusion during the next
run is more like a logical error.

--
Gerhard Menzl

#dogma int main ()

Humans may reply by replacing the thermal post part of my e-mail address
with "kapsch" and the top level domain part with "net".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Next: C++/CLI limitations?