Topic: Multiple C++ Exceptions
Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/03/26 Raw View
In article <38DA6074.DF507F6F@artquest.net>, Pierre Baillargeon
<pb@artquest.net> writes
>As I said above, in current programs, that is most certainly true. They
>were
>not designed to handle this.
And in my view supporting overlapping exceptions would result in heavy
extra performance/resource costs being paid by everyone. In contexts
where resource managers can fail to release their managed resources
there are other less pervasive solutions.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: fjh@cs.mu.OZ.AU (Fergus Henderson)
Date: 2000/03/26 Raw View
Francis Glassborow <francis@robinton.demon.co.uk> writes:
>In article <38DA6074.DF507F6F@artquest.net>, Pierre Baillargeon
><pb@artquest.net> writes
>>As I said above, in current programs, that is most certainly true. They
>>were
>>not designed to handle this.
>
>And in my view supporting overlapping exceptions would result in heavy
>extra performance/resource costs being paid by everyone.
Could you explain why you came to that view?
I don't see how it would have any significant performance costs. As far as I
can tell, the only cost it would have would be a very little bit of additional
time when an exception is thrown, and since throwing exceptions is already
expensive in most C++ implementations, the relative increase would be minimal.
I don't think there would need to be any significant time or space costs in
the common case where no exceptions occur.
--
Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3 | -- the last words of T. S. Garp.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: kanze@gabi-soft.de
Date: 2000/03/28 Raw View
Pierre Baillargeon <pb@artquest.net> writes:
|> I believe that the source of incomprehension is that I am talking
|> about failure in a class whose mandate is only to manage the
|> resource. For example, auto_ptr. The auto_ptr class is not the
|> memory but the manager of the memory. Where is the problem, in an
|> imaginary system where freeing memory could fail, that auto_ptr
|> throw an exception that contain the pointer to the memory that could
|> not be freed?
In such a system, auto_ptr is not an acceptable solution, period.
In general, once a destructor has started to run, you have no second
chance. (This has nothing to do with exceptions.) This means that in
some sense, the destructor *must* work -- at the very least, no matter
what happens, after the destructor has run, the object may hold no more
resources, and the rest of the system should be coherent, or at least as
coherent as it was before the destructor started.
This doesn't mean that things can't go wrong in a destructor. It does
mean that whatever goes wrong cannot be something which would cause a
problem later in the application.
A good example of this is an open file. Normally, when writing to file,
it is a serious error to count on the destructor to close the file --
you *need* to know if the close succeeds in order to know whether you
can further use the file. On the other hand, it would be a fatal error
to leave the file open if the ofstream were destructed during stack
unwinding due to another exception. In such cases, the close releases
resources, and the rest of the program supposes that the close failed
(supposing that it cares at all).
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Dave Abrahams <abrahams@mediaone.net>
Date: 2000/03/28 Raw View
in article 38D93C6E.1E621DA6@artquest.net, Pierre Baillargeon at
pb@artquest.net wrote on 3/23/00 12:30 PM:
> Dave Abrahams wrote:
>>
>>
>> The big problem with throwing from a destructor (and one of the reasons for
>> termination if it happens during unwinding) is that the object doesn't get
>> fully destroyed. Your proposal doesn't do anything to address that problem.
>> What does it mean to have a half-destroyed object lying around? You can't
>> use it. You can't destroy it...
>
> Well, first off, the reason the problem exists is due to the way the
> language is defined. The only reason the members and the base class
> don't get destroyed is because the language says so.
That argument could be made about anything in the language ;)
> Of course, it would
> change the definition of C++ and would break a lot of code.
Probably not so much. But right now, you have the guarantee that objects are
destroyed in the reverse order of construction:
struct XY {
X x; // All Y instances must be destroyed before X goes away
Y y;
};
This is a very powerful guarantee. If Y::~Y() throws an exception, we know
we won't execute X::~X(). It sounds like you're suggesting that the x should
be destroyed regardless of exceptions during the destruction of Y. Normally,
when an exception propagates out of a function, it means that the function
did *not* complete. So if Y isn't destroyed, should we go ahead and destroy
X anyway?
> Classes
> would have to be coded with the expectation that the sub-class
> destructor has failed. But then again, base classes are not aware of
> their sub-class, so this is not necessarily an issue.
Yeah, but it might well be an issue (see above).
> But I don't expect
> the language to change in this respect for a few dozen years...
>
> On the other hand, my experience has shown me that RAII classes handle a
> single resource,
RAII is an idiom; I don't know what you mean by an RAII class, but I've
written classes which handle multiple resources using this idiom myself.
> so there are no other resource to clean-up. Also, the
> exception thrown can easily contain the information needed to re-use the
> resource if needed. For example: the file handle for a file manager
> class, the transaction ID for a transaction manager class, etc.
You're suggesting that a file which failed to close could be re-used
somehow?
> The problem is for classes managing multiple resources. But these type
> of classes are inherently *not* exception safe!
Care to explain that assertion? Look at your std::list for example, which
manages multiple list nodes. About as exception-safe as most people could
want, as far as I can tell (if your implementation is conforming).
>> RAII is intimately tied to exception-handling. I don't understand in what
>> sense they need to be reconciled.
>
> Well, maybe it was not clear from my OP, but the problem lies in that a
> different mechanism has to be put in place to report errors that occur
> in a destructor. One has to wonder what benefits exceptions have if you
> have to put in place another mechanism on top to handle errors in
> destructor. Why not use *only* that other mechanism and save the "double
> standard" of errors?
It would be great if we could stop worrying about exceptions thrown in
destructors, and I think your ideas have some potential. It might even be
worth sacrificing (someday) the guarantee that earlier bases and members
won't be destroyed unless later ones are successfully destroyed, but this is
not to be undertaken lightly. There are probably other issues lurking here,
and each one would need to be considered carefully.
-Dave
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/28 Raw View
Dave Abrahams wrote:
>
> in article 38D93C6E.1E621DA6@artquest.net, Pierre Baillargeon at
> pb@artquest.net wrote on 3/23/00 12:30 PM:
>
> struct XY {
> X x; // All Y instances must be destroyed before X goes away
> Y y;
> };
>
> This is a very powerful guarantee. If Y::~Y() throws an exception, we know
> we won't execute X::~X(). It sounds like you're suggesting that the x should
> be destroyed regardless of exceptions during the destruction of Y. Normally,
> when an exception propagates out of a function, it means that the function
> did *not* complete. So if Y isn't destroyed, should we go ahead and destroy
> X anyway?
Well, if you replace "struct XY" with "void XY()" the answer goes from
no to yes. When re-working code to encapsulate data and behavior into
classes, this has to be taken into account. In this respect, the issue
of exceptions in destructor is related to coupling. The C++ language
implicitly declares that variables in a structure are fundamentally more
coupled than the same in a function.
Is that true? I have no statistic to provide on this, so my opinion is
as good as yours. It is true by decree of the C++ language, so people
write functions and classes with this in mind. But it's a choice forced
upon the designer by the language. I sometimes wished I could decide
when to call the super-class constructor and destructor, or the
initialization of member variables. This could be useful for locking, or
initializing multiple constants that depend on the same calculation. I
believe Java is different in this respect: you can have some code before
the call to super(), or before setting member variables.
The C++ trick is to have another function do the processing and calling
it as part of the initialization expression. Sometimes, it can get quite
contrived.
> > so there are no other resource to clean-up. Also, the
> > exception thrown can easily contain the information needed to re-use the
> > resource if needed. For example: the file handle for a file manager
> > class, the transaction ID for a transaction manager class, etc.
>
> You're suggesting that a file which failed to close could be re-used
> somehow?
Well, because we are talking in the abstract, it is difficult to answer.
In the concrete example of the file, I've never seen a close() API that
was allowed to fail, so this is not a good starting point. But in the
case of a flush() that fails, I could very well see that the file handle
could be used in some way, to delete the file instead of merely closing
it, for example.
>
> > The problem is for classes managing multiple resources. But these type
> > of classes are inherently *not* exception safe!
>
> Care to explain that assertion? Look at your std::list for example, which
> manages multiple list nodes. About as exception-safe as most people could
> want, as far as I can tell (if your implementation is conforming).
Well, the usual trick to manage two resources is to use the pimple idiom
twice, once for each resource, which is equivalent to having two classes
with a single resource. The list is an extreme example of this. In fact,
I believe that all standard containers use the pimple idiom in some way:
none contains directly another class as a member. They all have pointers
internally.
The sentence your quoted was meant in the context of a class that
*directly* contain two resources. Otherwise, like I said, it is just
like having two classes.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Hyman Rosen <hymie@prolifics.com>
Date: 2000/03/28 Raw View
Pierre Baillargeon <pb@artquest.net> writes:
> I believe Java is different in this respect: you can have some code before
> the call to super(), or before setting member variables.
You can not have any code before the call to super().
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: fjh@cs.mu.OZ.AU (Fergus Henderson)
Date: 2000/03/28 Raw View
Dave Abrahams <abrahams@mediaone.net> writes:
>Pierre Baillargeon wrote:
>
>> Also, the
>> exception thrown can easily contain the information needed to re-use the
>> resource if needed. For example: the file handle for a file manager
>> class, the transaction ID for a transaction manager class, etc.
>
>You're suggesting that a file which failed to close could be re-used
>somehow?
You can't reuse the file object after it has been destroyed, but
you can certainly reuse the file handle after a close failed,
at least for certain kinds of failure, particularly failures
that occur while flushing the buffers.
For example, if the close failed because the user removed the floppy disk
from the drive, then you can prompt the user to reinsert the disk
and then continue where you left off.
Similarly, if the close failed because there was no disk space,
you can prompt the user to free up some disk space, and then
continue where you left off.
Since the file object can't be used once it has been destroyed, if the file
object's destructor fails, it must store the information needed to resume the
operation (e.g. the file handle) in a new object -- specifically, in the
exception object that it throws.
--
Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3 | -- the last words of T. S. Garp.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: fjh@cs.mu.OZ.AU (Fergus Henderson)
Date: 2000/03/29 Raw View
kanze@gabi-soft.de writes:
>In general, once a destructor has started to run, you have no second
>chance. (This has nothing to do with exceptions.) This means that in
>some sense, the destructor *must* work -- at the very least, no matter
>what happens, after the destructor has run, the object may hold no more
>resources, and the rest of the system should be coherent, or at least as
>coherent as it was before the destructor started.
I think the idea with allowing multiple concurrent exceptions is that after
the destructor has run, these properties will indeed hold -- the object will
have been destroyed, and any resources that it could not release will be
handed to the caller in the form of exceptions; it then becomes the caller's
responsibility to deal with the failed attempts to release those resources
when it handles those exceptions.
>This doesn't mean that things can't go wrong in a destructor. It does
>mean that whatever goes wrong cannot be something which would cause a
>problem later in the application.
>
>A good example of this is an open file. Normally, when writing to file,
>it is a serious error to count on the destructor to close the file --
>you *need* to know if the close succeeds in order to know whether you
>can further use the file.
If the close fails, then you'll get an exception, which will alter
the normal control flow. If a subroutine throws an exception, the
program should not assume that that subroutine has completed its
tasks (e.g. finished writing out the file).
With support for multiple exceptions, the same would apply even if
the throw occurs during stack unwinding. So long as you throw an appropriate
exception (concurrently with the one that originally caused stack unwinding),
and so long as the program responds appropriately to such exceptions, I don't
think there should be any problem.
>On the other hand, it would be a fatal error
>to leave the file open if the ofstream were destructed during stack
>unwinding due to another exception. In such cases, the close releases
>resources, and the rest of the program supposes that the close failed
>(supposing that it cares at all).
Certainly the program ought to care; if the close failed, the program should
attempt to perform recovery actions, log the error, and/or report the error to
the user. The problem with the current exception handling scheme in C++
is that at the point where the failure happens -- in the destructor for
a file object, for example -- the code doesn't know how to properly handle
the failure, but also has no language-supported way of reliably conveying that
failure to the caller. If the exception occurred during stack unwinding,
the only easy choices are to simply ignore the error (which is a bad idea),
or to abort the program (which is also a bad idea).
--
Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3 | -- the last words of T. S. Garp.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Lisa Lippincott <lisa_lippincott@bigfix.com>
Date: 2000/03/30 Raw View
Dave Abrahams <abrahams@mediaone.net> puzzles me:
> But right now, you have the guarantee that objects are
> destroyed in the reverse order of construction:
>
> struct XY {
> X x; // All Y instances must be destroyed before X goes away
> Y y;
> };
>
> This is a very powerful guarantee. If Y::~Y() throws an exception, we know
> we won't execute X::~X(). It sounds like you're suggesting that the x should
> be destroyed regardless of exceptions during the destruction of Y. Normally,
> when an exception propagates out of a function, it means that the function
> did *not* complete. So if Y isn't destroyed, should we go ahead and destroy
> X anyway?
OK, I'm confused. Isn't this covered by paragraph 2 of 15.2 [except.ctor]?
An object that is partially constructed or partially destroyed will
have destructors executed for all of its fully constructed subobjects,
that is, for subobjects for which the constructor has completed
execution and the destructor has not yet begun execution.
It seems to me that if Y::~Y throws an exception, X::~X will
still be called. (And I think that's a fine thing.)
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Herb Sutter <hsutter@peerdirect.com>
Date: 2000/03/31 Raw View
On Thu, 30 Mar 2000 04:19:22 CST, Lisa Lippincott
<lisa_lippincott@bigfix.com> wrote:
>Dave Abrahams <abrahams@mediaone.net> puzzles me:
>> But right now, you have the guarantee that objects are
>> destroyed in the reverse order of construction:
>>
>> struct XY {
>> X x; // All Y instances must be destroyed before X goes away
>> Y y;
>> };
[...]
> An object that is partially constructed or partially destroyed will
> have destructors executed for all of its fully constructed subobjects,
> that is, for subobjects for which the constructor has completed
> execution and the destructor has not yet begun execution.
>
>It seems to me that if Y::~Y throws an exception, X::~X will
>still be called. (And I think that's a fine thing.)
Yes, except that if X::~X() then also can throw, -no- use of XY is
possible without risking program termination. Example:
{
XY xy;
} // destruction might terminate program: there is no defense
It's actually much worse than that: Assume that X::~X() is the ONLY
destructor in the entire program that can throw. Assume further that Y
construction is the ONLY OTHER operation in the whole program that can
throw. Then XY cannot be used safely, in any context whatsoever, period
-- even simply declaring an XY object can crash the program:
XY xy; // construction attempt might terminate program:
// 1. construct x -- succeeds
// 2. construct y -- fails, throws exception
// 3. clean up by destroying x -- fails, throws exception,
// but an exception is already active, so call terminate()
// (oops)
// there is no defense
So it is highly dangerous to have even one destructor that could throw.
Never, ever, ever write destructors that can throw.
Herb
---
Herb Sutter (mailto:hsutter@peerdirect.com)
CTO, PeerDirect Inc. (http://www.peerdirect.com)
Chair, ANSI H2 Ad-Hoc for SQL Part 12: SQL/Replication
(Replication working group, ANSI SQL committee)
Editor-in-Chief, C++ Report (http://www.creport.com)
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/03/24 Raw View
In article <38D93C6E.1E621DA6@artquest.net>, Pierre Baillargeon
<pb@artquest.net> writes
>Well, first off, the reason the problem exists is due to the way the
>language is defined. The only reason the members and the base class
>don't get destroyed is because the language says so. Of course, it would
>change the definition of C++ and would break a lot of code. Classes
>would have to be coded with the expectation that the sub-class
>destructor has failed. But then again, base classes are not aware of
>their sub-class, so this is not necessarily an issue. But I don't expect
>the language to change in this respect for a few dozen years...
I do not expect this to ever change.
>
>On the other hand, my experience has shown me that RAII classes handle a
>single resource, so there are no other resource to clean-up. Also, the
>exception thrown can easily contain the information needed to re-use the
>resource if needed. For example: the file handle for a file manager
>class, the transaction ID for a transaction manager class, etc.
Sorry, but my mind simply boggles.
>
>The problem is for classes managing multiple resources. But these type
>of classes are inherently *not* exception safe! Do you see another
>problem I have missed?
>From my perspective there seems to be a deep flaw in this thinking about
exceptions and destructors. Exceptions are designed to handle
exceptional circumstances. Basically something has gone wrong and we
wish to attempt a recovery. Stack unwinding and the calling of dtors is
part of that recovery process. It makes perfect sense (and I believe C++
supports it) to put one problem aside while you completely handle
another (nested exceptions) but what would inevitably result in chaos is
to have overlapping handling of exceptions. dtors that throw (note they
are not banned) are a strong indicator that this will happen. An
exception thrown by the dtor of a sub-object is almost certainly a
symptom of a catastrophic failure.
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Bill Wade" <bill.wade@stoner.com>
Date: 2000/03/24 Raw View
Pierre Baillargeon wrote in message <38D93C6E.1E621DA6@artquest.net>...
>Well, maybe it was not clear from my OP, but the problem lies in that a
>different mechanism has to be put in place to report errors that occur
>in a destructor. One has to wonder what benefits exceptions have if you
>have to put in place another mechanism on top to handle errors in
>destructor. Why not use *only* that other mechanism and save the "double
>standard" of errors?
It can certainly be seductive to look for the "one true solution" to a
problem. However that solution may be difficult to find.
What is the "one true solution" to the data type you should use to represent
numeric values in any program? Is it int, complex<double> or an
infinite-precision rational type? Finding the answer to my first question
may provide insight into your question.
Regards
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/24 Raw View
Francis Glassborow wrote:
>
> In article <38D93C6E.1E621DA6@artquest.net>, Pierre Baillargeon
> <pb@artquest.net> writes
> >their sub-class, so this is not necessarily an issue. But I don't expect
> >the language to change in this respect for a few dozen years...
>
> I do not expect this to ever change.
I'm not very good at conveying sarcasm I suppose. Of course I don't
expect
such a dramatic change in the language to ever happen!
> >
> >On the other hand, my experience has shown me that RAII classes handle a
> >single resource, so there are no other resource to clean-up. Also, the
> >exception thrown can easily contain the information needed to re-use the
> >resource if needed. For example: the file handle for a file manager
> >class, the transaction ID for a transaction manager class, etc.
>
> Sorry, but my mind simply boggles.
Could you elaborate?
I believe that the source of incomprehension is that I am talking
about failure in a class whose mandate is only to manage the resource.
For example, auto_ptr. The auto_ptr class is not the memory but the
manager of the memory. Where is the problem, in an imaginary system
where
freeing memory could fail, that auto_ptr throw an exception that contain
the pointer to the memory that could not be freed?
> It makes perfect sense (and I believe C++
> supports it) to put one problem aside while you completely handle
> another (nested exceptions) but what would inevitably result in chaos is
> to have overlapping handling of exceptions. dtors that throw (note they
> are not banned) are a strong indicator that this will happen.
I believe that your reaction is the result of overlooking a single fact:
what I'm talking about is non- existent. Of course, *current* program
and
*current* design rules are not ready to handle overlapping handling of
exceptions.
To me, that reaction is equivalent to compiling a C program with a C++
compiler and saying that maintenance should not introduce anything that
could throw any exception because it would "inevitably result in chaos".
Of course! The original program was not designed for this!
If overlapping exceptions where supported, it would require a new style
of design to take advantage of it, just like using exceptions now
requires
a new style of design when compared to using return codes.
I would even turn your comment upside down:
Exceptions in C++ exist. Due to the problem with double exceptions,
it is a very good design rule to ban "leaking" an exception from any
destructor. That doesn't mean that exceptions have to be banned
interely.
You only have to carefully design your classes and your destructor to
follow the rule.
Now, let's say that overlapping exceptions are allowed, like I
described.
Would "good design rules" change? Yes! You would now either not throw
from
a destructor, as before, -or- design your classes and your exceptions
such
that you won't leak resources.
What I fail to understand is as follow.
Throwing an exception is a mechanism to delocalize the handling of
errors.
The point of failure signal an error and some levels above it, something
will catch it and react. Why is it that if *two* failures occur in
succession, the second while unwinding the stack, that it it impossible
to
recover?
I fully understand the problem of resource leak. That is why the
*resource*
destructor should never throw. But a *resource manager* could throw in
its
destructor. I now see that this central fact was not conveyed clearly. I
though that by talking about double exception in the context of RAII,
that
it would be clear.
Maybe I should explain where I come from, figuratively. Lately, I've
designed
a module to build up the content of a complex document structure. I
aimed to
avoid errors by making small classes that each build a part of a
document and
where the constructor argument are such that the programmer can't make
mistake
in the document structure. But to achieve this in nested parts, the
destructor
must be the one "closing" the document structure, which mean that it
does
some simple processing and I/O.
I've found that, indeed, this design does make the document structure
very
clear, the program self-documenting, and structural errors inexistant.
But
I cannot report errors from the destructor using exceptions.
Currently I simply sets error code. The other alternative that I did not
try
was to create an "accumulative" exception class. That is, a singleton
pattern manages the throwing of exceptions (through a throwException()
method for example) and decide to either throw an exception it controls
or "add" an error inside the current active exception.
> An
> exception thrown by the dtor of a sub-object is almost certainly a
> symptom of a catastrophic failure.
As I said above, in current programs, that is most certainly true. They
were
not designed to handle 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 2000/03/17 Raw View
Andrei Alexandrescu wrote:
> I don't agree with the method of throwing an exception
> from the destructor. It means that if a file is destroyed as part of
> throwing an exception elsewhere, the program abort()s.
Proponent> I propose that we introduce the keyword final in C++.
Proponent> I would have the same semantic as in Java (non-virtual
Proponent> member function).
Opponent> I don't agree with the method of writing
Opponent> class A {
Opponent> final void foo ();
Opponent> ^^^^^
Opponent> };
Opponent> because the C++ grammar only allows inline,
Opponent> virtual, or static here.
Maybe you should read all the articles mentioned in
the references header before replying, not just the
ones quoted in the article you are following-up to.
--
Valentin Bonnard
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 2000/03/17 Raw View
Francis Glassborow wrote:
> In article <38CE793F.4570@wanadoo.fr>, Valentin Bonnard
> <Bonnard.V@wanadoo.fr> writes
> >That's why I want to be able to throw exceptions in dtors: that's the
> >only place where I understand them.
>
> You mean in dtors or from dtors?
I don't really care: it was a joke anyway.
> It is the leaking of an exception from
> a dtor that causes massive problems.
It's the kind of problem that a different language spec
could perhaps avoid, at the cost of simplicity.
--
Valentin Bonnard
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Dave Abrahams <abrahams@mediaone.net>
Date: 2000/03/23 Raw View
in article 38C681FD.1513E64@artquest.net, Pierre Baillargeon at
pb@artquest.net wrote on 3/9/00 5:52 AM:
> Using the catch_all and catch_one keywords we have the following
> benefits.
>
> 1. We get rid of the ugly ways multiple exceptions are treated in the
> current C++ standard, that is the abortion of the program.
The current C++ standard doesn't disallow multiple exceptions, or require
termination, neccessarily:
void f()
{
try {
throw 0;
}
catch(...) {
try {
throw "a string"; // now multiple exceptions are active
}
catch(...) {}
throw; // rethrows 0
}
}
> 2. We fix the problem of not being able to throw exception from a
> destructor reliably. Now as many exception as needed can be active.
The big problem with throwing from a destructor (and one of the reasons for
termination if it happens during unwinding) is that the object doesn't get
fully destroyed. Your proposal doesn't do anything to address that problem.
What does it mean to have a half-destroyed object lying around? You can't
use it. You can't destroy it...
> 3. We reconcile RAII with exceptions.
RAII is intimately tied to exception-handling. I don't understand in what
sense they need to be reconciled.
There's-no-free-lunch-ly y'rs,
Dave
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/23 Raw View
Dave Abrahams wrote:
>
>
> The big problem with throwing from a destructor (and one of the reasons for
> termination if it happens during unwinding) is that the object doesn't get
> fully destroyed. Your proposal doesn't do anything to address that problem.
> What does it mean to have a half-destroyed object lying around? You can't
> use it. You can't destroy it...
Well, first off, the reason the problem exists is due to the way the
language is defined. The only reason the members and the base class
don't get destroyed is because the language says so. Of course, it would
change the definition of C++ and would break a lot of code. Classes
would have to be coded with the expectation that the sub-class
destructor has failed. But then again, base classes are not aware of
their sub-class, so this is not necessarily an issue. But I don't expect
the language to change in this respect for a few dozen years...
On the other hand, my experience has shown me that RAII classes handle a
single resource, so there are no other resource to clean-up. Also, the
exception thrown can easily contain the information needed to re-use the
resource if needed. For example: the file handle for a file manager
class, the transaction ID for a transaction manager class, etc.
The problem is for classes managing multiple resources. But these type
of classes are inherently *not* exception safe! Do you see another
problem I have missed?
> > 3. We reconcile RAII with exceptions.
>
> RAII is intimately tied to exception-handling. I don't understand in what
> sense they need to be reconciled.
Well, maybe it was not clear from my OP, but the problem lies in that a
different mechanism has to be put in place to report errors that occur
in a destructor. One has to wonder what benefits exceptions have if you
have to put in place another mechanism on top to handle errors in
destructor. Why not use *only* that other mechanism and save the "double
standard" of errors?
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/09 Raw View
-- Preamble --
I recently started thinking about how multiple errors were handled in
the past. For example in batch system, when treating files 1 to 10, an
error in file 2 does not stop the batch job. The error files are put
aside to be retried and a log of the errors is done.
I had a similar design in one project where errors where accumulated and
a single exception was thrown containing all errors.
But both cases could be seen as sequential errors, not nested ones, so
they are not equivalent to the multiple exceptions problem.
What really kicked the my imagination was another analogy. It was about
a tool we all use everyday and which handles multiple recursive,
cascading errors, with no problem. That tool, of course, is a C++
compiler. Isn't funny that the tool used to treat C++ program can handle
errors in a way C++ are not allowed to?
-- Problem --
A recent thread in c.l.c++.m about "resource acquisition is
initialization" made me revisit the problem of throwing exceptions
during stack unwinding caused by another exception.
The problem is that if one wants to free the resource in the destructor,
then one looses the ability of signaling errors during that phase using
exceptions. There seem to be a conflict between RAII and "never throw
from destructor".
Currently one must use alternative methods to signal errors, like static
variable in the class, or a log system.
So to reconcile RAII and exceptions, one would need the capabilities to
handle multiple exceptions at the same time. That is the proposal I
would like to explore.
-- Proposal --
To put it simply, all that is needed is two new keywords or two new
syntaxic constructions. For the time being, lets call them catch_all and
catch_one. The idea is that catch_all catches all pending exceptions
matching its specification, and catch_one catches a single exception.
The existing catch keyword could be mapped to either one, but catch_one
seems more appropriate.
The mechanic of catch_one is just like the current catch keyword. A
single exception is catched and treated. The only difference is that if
the exception is not rethrown, the other exceptions (if any) are
rethrown automatically. This automatic rethrowing is much like what
happens in a constructor try/catch block.
The mechanic of the catch_all is that the catch block is re-entered for
each exception matching the specification. For example, if there are
three exceptions matching the specification, then the block is entered
three times, once for each exception. Like the catch_one block, any
other exception is rethrown after this processing.
Two important points must be considered when matching exceptions.
The first point is that an exception can only be catched once per serie
of catch blocks. That is, if there are multiple catch block in a single
scope, only one can handle the exception, even if rethrown.
The second point is that in both case (catch_one and catch_all), a
different exception from the one catched can be rethrown, just as it is
possible in the current C++ standard. In both cases (same exception,
different exception), the rethrown exception cannot be catched by the
current catch_all block. Otherwise it would be impossible to rethrow the
same exception from a catch_all block.
In short, the mechanic of the multiple exceptions system is just like
the current one, except that there is a set of exceptions considered
each time a catch block is seen instead of a single exception.
A few examples:
[ example one: simple case --
Active exceptions, listed by their type: A, B, B, C.
The catch blocks:
catch ( A )
{
}
catch_all ( B )
{
}
Result: the A block is entered one time, the B block twice, then the C
exception is rethrown.
-- end example ]
[ example two: derived exception types --
Active exceptions, listed by their type: A, A.
Exception type A derives from type B.
The catch blocks:
catch ( A )
{
}
catch_all ( B )
{
}
Result: the A block is entered one time, then the B block catches the
second A.
-- end example ]
[ example three: an exception can only be catched once per scope --
Active exceptions, listed by their type: A, A.
Exception type A derives from type B.
The catch blocks:
catch ( A )
{
throw;
}
catch_all ( B )
{
}
Result: the A block is entered one time and rethrow the first A, then
the B block catches the second A, then the first A is rethrown.
-- end example ]
-- Difficulties --
The first apparent difficulty is that there could be untold amount of
active exceptions.
That is not true, as the number of exception is limited by the number of
objects currently on the stack. So it is not unbounded. On the contrary,
it has the same bound as the stack. Note that the C++ standard does not
treat what happens when the normal stack is full. I believe it doesn't
even have the explicit notion of stack. So it doesn't need to address
what happens when there are no more space for other exception either.
The second apparent difficulty is how to keep the potentially large
amount of active exception.
Again, that is not true. First, the current C++ standard also force to
keep a potentially unbounded space for exceptions since a single
exception object could have an arbitrarely large size. So the same
mechanism used by implementor to handle large exception objects can be
used for multiple objects. The first difficulty even suggests that
having a stack-like construct to keep exception would be a good idea.
There must be other difficulties hidden somewhere in this proposal, but
I currently see none.
-- Conclusion --
Using the catch_all and catch_one keywords we have the following
benefits.
1. We get rid of the ugly ways multiple exceptions are treated in the
current C++ standard, that is the abortion of the program.
2. We fix the problem of not being able to throw exception from a
destructor reliably. Now as many exception as needed can be active.
3. We reconcile RAII with exceptions.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 2000/03/10 Raw View
Pierre Baillargeon wrote:
> The problem is that if one wants to free the resource in the destructor,
> then one looses the ability of signaling errors during that phase using
> exceptions.
Your proposal is very interresting.
I have proposed a different solution to this problem (see Deja, this
newsgroup, a long time ago, exceptions in destructors); in short,
when an exception is thrown in a destructor during a stack walk-back,
a handler function is called:
void handle_double_exception (const any_type& first, const any_type&
second);
This function can either:
- call terminate (old behaviour)
- throw another exception, that summarize both exceptions
Many people seem to strongly dislike multiple exceptions. Some
just don't want to ``open a can of worm''.
> The first apparent difficulty is that there could be untold amount of
> active exceptions.
> Note that the C++ standard does not
> treat what happens when the normal stack is full.
Indeed, see 1.4/2:
a conforming implementation shall, within its resource limits,
^^^^^^^^^^^^^^^^^^^^^^^^^^
accept and correctly execute that program.
> I believe it doesn't
> even have the explicit notion of stack.
You are right.
> The second apparent difficulty is how to keep the potentially large
> amount of active exception.
This is already the case: a program can create an unlimited amount
of exceptions at the same time (just that currently, only one is
active at any time).
What other languages do
-----------------------
When an exception escapes a destructor, I believe that C++ and
Ada abort, Java ignores it. When a function in a catch block
throws, ML, C++, Ada and Java all discard the previous exception
and consider the thrown exception to be the current one.
I don't know any languages with multiple active exceptions.
--
Valentin Bonnard
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "John Hickin" <hickin@nortelnetworks.com>
Date: 2000/03/10 Raw View
Pierre Baillargeon wrote:
>
>
> -- Problem --
>
> A recent thread in c.l.c++.m about "resource acquisition is
> initialization" made me revisit the problem of throwing exceptions
> during stack unwinding caused by another exception.
>
> The problem is that if one wants to free the resource in the destructor,
> then one looses the ability of signaling errors during that phase using
> exceptions. There seem to be a conflict between RAII and "never throw
> from destructor".
>
No, IMHO they go hand in hand.
Let's take an ordinary class with a constructor and destructor. Any
class. If the constructor throws there is no object, no problem, etc.;
everybody is happy. The logical consequence is that the object should
immediately destructible without an exception being raised, i.e.,
do { T t; } while(0);
either produces an exception in T::T or no exception at all. If the
situation is otherwise I consider that T is broken. If T is not broken
there can be no error that can be signaled from its destructor. Now lets
complicate things a bit:
do { T t; t.use(); } while(0);
If use() places t in a state where ~T might throw then the contract of T
is broken; you must instead code like this:
do { t t; t.use(); ensure_destructible(t); }
Often a simple way to ensure this is to use pimpl. ensure_destructible
can steal the impl pointer and hand it off to some object where its
destruction is delayed until it is safe to do so. Of course this means
that resources held by the impl might no longer be released in a timely
fashion. I've seen this used when dealing with voice channel resources;
a good means of avoiding a race on hangup is to put the channel on a
list of recently used ones where it will stay until the probability that
it hasn't yet been properly cleaned up is sufficiently low.
If you acquire a resource and can't immediately release it then I think
that something is very wrong. As a specific example consider locks:
class LockGuard {
Lock& l;
public:
struct LockException : public std::exception { .. };
LockGuard( Lock& l ) throw(LockException) : l(l) { if ( !l.acquire()
) throw LockException(l.error()); }
~LockGuard() throw() { l.unlock(); }
};
In this example I see no way that I can recover from an unlock failure.
I'd rather code the destructor as
~LockGuard() { l.unlock() == FAILURE && pause_forever(); }
and leak the whole thread. If it has a mutex then I leak the whole
subsystem to which the thread belongs. If I leak enough of those
subsystems I escalate by committing suicide. If I really need to signal
an error from my destructor I can place a message in a queue or write
some information to a pipe before pausing; then some sanity checking
thread or process will see an event and take action.
And by the way, I like my designs to tolerate dirty kills:
kill(getpid(),SIGKILL) or
TerninateProcess(GetCurrentProcess(),exitCode)
counting on the OS (or a parent process which has access to my state in
shared memory) to clean up what I can't; if I've done things _correctly_
I won't need a device driver to save my bacon.
Regards, John.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/11 Raw View
John Hickin wrote:
>
> Pierre Baillargeon wrote:
> > The problem is that if one wants to free the resource in the destructor,
> > then one looses the ability of signaling errors during that phase using
> > exceptions. There seem to be a conflict between RAII and "never throw
> > from destructor".
>
> No, IMHO they go hand in hand.
>
[...]
>
> If use() places t in a state where ~T might throw then the contract of T
> is broken; you must instead code like this:
>
> do { t t; t.use(); ensure_destructible(t); }
>
But this completely circumvent the idea of RAII, no? You are back to
calling the "undo" function by hand. If you consider a non-trivial
example like the following, your guideline won't work:
do { t t; t.use(); /*use the result of t.use()*/;
ensure_destructible(t); }
If "use the result of t.use()" throws, you are out of luck. You gave the
example of a lock, which I never seen fail on release, but there are
things that do: cache flush, database commit, etc. Currently, the only
way to write such code is to have a try block with a catch block within
which all other "undoing" are done in a nested try block that eats all
exceptions and then rethrows the exception. Ugly. Error prone. Consider:
-- current situation without RAII, not-exception safe --
void foo(DB &db);
int main (int, char **)
{
DB db;
try
{
foo (db);
}
catch ( exception ex )
{
// do as you wish: log etc.
}
return 0;
}
void foo(DB &db)
{
File file ( "bar" ); // destr. leaks unflushed file to avoid
exception.
Transaction trans ( db.newTransaction() ); // idem for trans.
//...
trans.commit ( ); // must be called by hand due to possible exception.
file.flush ( ); // idem.
file.close ( ); // silently leaks a file on transaction exception.
}
-- current situation use RAII, exception safe and verbose --
void foo(DB &db);
int main (int, char **)
{
DB db;
try
{
foo (db);
}
catch ( exception ex )
{
// do as you wish: log etc.
}
return 0;
}
void foo(DB &db)
{
AutoClose<File> file ( "bar" ); // can't use RAII for flush(): could
throw.
try
{
AutoCommit<Transaction> trans ( db.newTransaction() );
//...
}
catch ( ... )
{
try
{
file.flush (); // duplicate code below.
}
catch (...)
{
// log or ignore.
}
throw;
}
try
{
file.flush (); // duplicate code above.
}
catch (...)
{
// log or ignore.
}
}
-- proposal implemented situation --
void foo(DB &db);
int main (int, char **)
{
DB db;
try
{
foo (db);
}
catch_all ( exception ex )
{
// do as you wish: log etc.
}
return 0;
}
void foo(DB &db)
{
AutoClose<AutoFlush<File> > file ( File ( "bar" ) );
AutoCommit<Transaction> trans ( db.startTransaction() );
// ...
// Note: throw in destructor of AutoFlush due to file.flush ()
// will propagate, but the file will be closed!
}
-- end --
I am very interested to see if you can show me how to use RAII in the
second example *without* defeating its prupose by requiring that a
special function be called just at the right moment. I would use it in
my own code!
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/11 Raw View
Valentin Bonnard wrote:
>
> Pierre Baillargeon wrote:
>
> I have proposed a different solution to this problem (see Deja, this
> newsgroup, a long time ago, exceptions in destructors); in short,
> when an exception is thrown in a destructor during a stack walk-back,
> a handler function is called:
>
> void handle_double_exception (const any_type& first, const any_type&
> second);
The problem I see with this solution is that the function is global
which mean that the implementor must know about all exception types.
Consider the problem of two libraries which internally use exceptions
not visible in the API. There are good reasons not to leak exception out
of a genric library. For example: not all your user are exception saavy.
There's also that funny "any_type" ...
[...]
> What other languages do
> -----------------------
>
> When an exception escapes a destructor, I believe that C++ and
> Ada abort, Java ignores it. When a function in a catch block
> throws, ML, C++, Ada and Java all discard the previous exception
> and consider the thrown exception to be the current one.
>
> I don't know any languages with multiple active exceptions.
I think I prefer to ignore than to abort. But this is tainted by the
fact that the usual behavior of my programs is to trap all exceptions in
main(), log the error and exit... This is mainly due to that fact that
I'm very aware that the libraries I'm using are not exception safe, thus
there's not point in pretending I can build safe code on top...
And no, the thrower could not do the logging as it would couple
low-level libraries with stuff they shouldn't be aware of.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 2000/03/13 Raw View
John Hickin wrote:
>
> Pierre Baillargeon wrote:
> >
>
> >
> > -- Problem --
> >
> > A recent thread in c.l.c++.m about "resource acquisition is
> > initialization" made me revisit the problem of throwing exceptions
> > during stack unwinding caused by another exception.
> >
> > The problem is that if one wants to free the resource in the destructor,
> > then one looses the ability of signaling errors during that phase using
> > exceptions. There seem to be a conflict between RAII and "never throw
> > from destructor".
>
> No, IMHO they go hand in hand.
>
> Let's take an ordinary class with a constructor and destructor. Any
> class. If the constructor throws there is no object, no problem, etc.;
> everybody is happy. The logical consequence is that the object should
> immediately destructible without an exception being raised, i.e.,
Bananas are yellow. The logical consequence is that I am the
best C++ in the world.
> If the
> situation is otherwise I consider that T is broken.
If someone says that I am not the best C++ programmer in the
world I consider that his reasonning is broken.
> If you acquire a resource and can't immediately release it then I think
> that something is very wrong.
I already wrote about how to deal with errors in dtors, if C++
supported them well (exceptions in dtors have been discussed in
this group in many looong threads a long time ago -- see Deja);
I will repeat it here:
class File {
public:
File (string name) : file_desc (... open file )
~File () { .. flush and close file_desc
if (an error occured during flush or close)
throw CloseError (file_desc); // don't erase data
// associated with file_desc
desallocate (file_desc); // reclaim memory associated with
file_desc
}
};
try { ... }
catch (CloseError err)
{
// ensure more space is available for flusing
flush (err.file_desc);
close (err.file_desc);
desallocate (file_desc);
}
--
Valentin Bonnard
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "John Hickin" <hickin@nortelnetworks.com>
Date: 2000/03/14 Raw View
Valentin Bonnard wrote:
>
>
> Bananas are yellow. The logical consequence is that I am the
> best C++ in the world.
Sorry, that doesn't follow. I guess you are being facetious. I don't
particularly like the tone of your reply. Perhaps you would consider
being more polite in the future.
>
> If someone says that I am not the best C++ programmer in the
> world I consider that his reasonning is broken.
This is plain rude. It adds nothing to the discussion. Until now I had a
high opinion of your postings to the C++ newsgroups.
>
>
> I already wrote about how to deal with errors in dtors, if C++
> supported them well (exceptions in dtors have been discussed in
> this group in many looong threads a long time ago -- see Deja);
> I will repeat it here:
>
> class File {
> public:
> File (string name) : file_desc (... open file )
> ~File () { .. flush and close file_desc
> if (an error occured during flush or close)
> throw CloseError (file_desc); // don't erase data
> // associated with file_desc
> desallocate (file_desc); // reclaim memory associated with
> file_desc
> }
> };
>
> try { ... }
> catch (CloseError err)
> {
> // ensure more space is available for flusing
> flush (err.file_desc);
> close (err.file_desc);
This isn't as safe as it could be. close() may fail. It some situations
it may be interrupted by a signal; in others it may time out. Perhaps
you know how to deal with errors in dtors but outside them seems to be a
different story.
Sorry, I couldn't resist.
> desallocate (file_desc);
> }
>
John.
//
======================================= MODERATOR'S COMMENT:
Let's all try to be civil, please.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 2000/03/15 Raw View
Pierre Baillargeon wrote:
[...]
> Using the catch_all and catch_one keywords we have the following
> benefits.
>
> 1. We get rid of the ugly ways multiple exceptions are treated in the
> current C++ standard, that is the abortion of the program.
>
> 2. We fix the problem of not being able to throw exception from a
> destructor reliably. Now as many exception as needed can be active.
>
> 3. We reconcile RAII with exceptions.
I think the problem lies deeper.
The "best" exception behaviour is commit-or-rollback. And the
standard exception mechanism ist built on exactly that -
everything done (constructed objects) is undone (objects get
destructed), as far as the compiler can do it.
Especially for constructors, the rules do exactly that: Either
construction succeeds, then there's a completely constructed object
(i.e. commit). Or the constructor throws, then everything already
constructed gets destructed again, and we get the same state as
without destruction (i.e. rollback), except if the constructor
or destructor did a permanent change outside of the object itself
(in which case it's the user who made the behaviour different).
However, failures in destructors cannot work this way.
When the exception is thrown, parts of the object are
usually already destructed, and you usually cannot just
reconstruct them. That is, a rollback-or-commit strategy
for exceptions is not possible at all.
This on itself isn't a sufficient reason for banning throws
from destructors (there's other code where commit-or-rollback
isn't possible - think of triggering a gun), however it
already shows that destructors are different according to
exceptions.
Now the second special thing about destructors - which causes
the double-exception problem - is that they are called
during stack unwinding (i.e. during rollback). That is, if
a destructor throws during stack unwinding, it means that
rollback itself failed. Therefore a double exception describes
a more serious problem: Not only did the operation we tried
fail, but in addition we didn't even succeed reverting to
the old state (or to the new state we wanted to reach on failure).
That's a more severe error than just a failure to do something,
since it means that we didn't approach one of the defined states
we wanted to approach.
The standard committee obviously considered that situation
bad enough to unconditionally abort the program. I don't
agree with that, but I do think that this should be handled
immediatly, not just at some later catch. That is, there
should be some function "void double_exception()" similar to
the unexpected function. Whenever two exceptions clash, this
should be called. To access the exceptions, one could make
the following rules:
- If the function exits normally (i.e., without (re-)throwing
an exception), the program is aborted.
- If the function throws an exception (either a new one,
or one of the two which clashed), that exception is
propagated, the other one (or both, for a new exception)
is destroyed.
- The second thrown exception is the "active" one, that is,
a single "throw;" re-throws that. If that exception gets
finished, now the first one gets the active one.
I'm not completely satisfied with the third rule; however
I don't have a better idea - I definitively want to
distinguish which exception was thrown first.
An example could be:
void double_exception()
{
using namespace std;
try
{
throw;
}
catch (file_exception& f)
{
if (f.file != &logfile)
{
if (f.action == close_file)
{
try { (f.file->close(); } // try it again
catch(...) { cerr << "Could not close "
<< f.file->name() << endl; }
}
else
{
cerr << "Warning: File " << f.file->name()
<< "might be corrupted!" << endl
}
}
else
{
// ignore logging errors
}
}
catch(exit&)
{
throw; // exit has always maximum priority
}
catch(...)
{
terminate(); // something severe happened...
}
throw; // if we are here, the second error has been handled,
// therefore the first one is rethrown
}
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: 2000/03/15 Raw View
John Hickin <hickin@nortelnetworks.com> wrote in message
news:38CD018A.92971C5D@nortelnetworks.com...
> Valentin Bonnard wrote:
> > class File {
> > public:
> > File (string name) : file_desc (... open file )
> > ~File () { .. flush and close file_desc
> > if (an error occured during flush or close)
> > throw CloseError (file_desc); // don't erase data
> > // associated with file_desc
> > desallocate (file_desc); // reclaim memory associated with
> > file_desc
> > }
> > };
> >
> > try { ... }
> > catch (CloseError err)
> > {
> > // ensure more space is available for flusing
> > flush (err.file_desc);
> > close (err.file_desc);
>
> This isn't as safe as it could be. close() may fail. It some situations
> it may be interrupted by a signal; in others it may time out. Perhaps
> you know how to deal with errors in dtors but outside them seems to be a
> different story.
I don't agree with the method of throwing an exception
from the destructor. It means that if a file is destroyed as part of
throwing an exception elsewhere, the program abort()s.
> > Bananas are yellow. The logical consequence is that I am the
> > best C++ in the world.
>
> Sorry, that doesn't follow. I guess you are being facetious. I don't
> particularly like the tone of your reply. Perhaps you would consider
> being more polite in the future.
Couldn't agree more. I had sent another reply to your message. It expressed
my opinion in a very polite manner, which I find quite an achievement given
the content I was referring to. However, my post was rejected.
There is this pattern that happens sometimes on moderated newsgroups. A
person sends a post. That person thinks it's a joyful, smart, brilliant
message. It's not. Instead, it's a sordid uninspired rude flame that doesn't
honor anybody. However, somehow the message makes it on the newsgroup.
Erring is human, etcetera.
Then, people try to reasonably express disagreement with the flame. However,
their posts are rejected, although they are far more passable than the
article they respond to.
> Sorry, I couldn't resist.
>
> > desallocate (file_desc);
> > }
Ditto. Send me private email and I'll send you my little killfile that makes
reading this newsgroup very enjoyable.
Andrei
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Pierre Baillargeon <pb@artquest.net>
Date: 2000/03/15 Raw View
Christopher Eltschka wrote:
>
> Pierre Baillargeon wrote:
>
> I think the problem lies deeper.
[...]
> However, failures in destructors cannot work this way.
> When the exception is thrown, parts of the object are
> usually already destructed, and you usually cannot just
> reconstruct them. That is, a rollback-or-commit strategy
> for exceptions is not possible at all.
The context of my discussion was the RAII pattern. One of the important
detail of the pattern is that each instantiation of the pattern only
manages a single resource. Yes, throwing from the destructor means that
the resource management failed.
[...]
> That is, if
> a destructor throws during stack unwinding, it means that
> rollback itself failed. Therefore a double exception describes
> a more serious problem: Not only did the operation we tried
> fail, but in addition we didn't even succeed reverting to
> the old state (or to the new state we wanted to reach on failure).
> That's a more severe error than just a failure to do something,
> since it means that we didn't approach one of the defined states
> we wanted to approach.
I believe that is where we differ. I don't consider resource management
failure as a different kind of failure, or one that is more critical.
There are some resources in which if the release fails then it is a
critical
failure of the program and it should abort. But I want to be able to use
RAII and exceptions all the time, and decide myself when a double
exception
should abort the program.
But then again you say so yourself:
> The standard committee obviously considered that situation
> bad enough to unconditionally abort the program. I don't
> agree with that,
But I disagree with:
> but I do think that this should be handled
> immediatly, not just at some later catch. That is, there
> should be some function "void double_exception()" similar to
> the unexpected function. Whenever two exceptions clash, this
> should be called.
The problem I see with this, as I said in another reply, is that such a
function has to be global, which is a bad thing: the implementor must
know all possible exceptions, and how to treat them all. Worse: a single
exception type can be treated differently in two different places in the
program. And if you can write such a global function, then why are you
throwing from the destructor in the first place and not doing what the
function does? The answer: locality of behavior. Which is exactly what
the catch, catch_all, catch_one, etc allow doing.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 2000/03/15 Raw View
John Hickin wrote:
> Valentin Bonnard wrote:
> > Bananas are yellow. The logical consequence is that I am the
> > best C++ in the world.
>
> Sorry, that doesn't follow. I guess you are being facetious.
That is not impossible.
> I don't
> particularly like the tone of your reply. Perhaps you would consider
> being more polite in the future.
I was paraphrasign your reasoning.
> > If someone says that I am not the best C++ programmer in the
> > world I consider that his reasonning is broken.
>
> This is plain rude. It adds nothing to the discussion. Until now I had a
> high opinion of your postings to the C++ newsgroups.
Again, I was using the same kind of arguments as you.
> > I already wrote about how to deal with errors in dtors, if C++
> > supported them well (exceptions in dtors have been discussed in
> > this group in many looong threads a long time ago -- see Deja);
> > I will repeat it here:
> >
> > class File {
> > public:
> > File (string name) : file_desc (... open file )
> > ~File () { .. flush and close file_desc
> > if (an error occured during flush or close)
> > throw CloseError (file_desc); // don't erase data
> > // associated with file_desc
> > desallocate (file_desc); // reclaim memory associated with
> > file_desc
> > }
> > };
> >
> > try { ... }
> > catch (CloseError err)
> > {
> > // ensure more space is available for flusing
> > flush (err.file_desc);
> > close (err.file_desc);
>
> This isn't as safe as it could be. close() may fail.
Of course, one could check a second time, have some other
recovery actions, then retry (then check if it failed, then
take yet another recovery mesures, then close again...)
> Perhaps
> you know how to deal with errors in dtors but outside them seems to be a
> different story.
That's why I want to be able to throw exceptions in dtors: that's the
only place where I understand them.
--
Valentin Bonnard
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/03/16 Raw View
In article <38CE793F.4570@wanadoo.fr>, Valentin Bonnard
<Bonnard.V@wanadoo.fr> writes
>That's why I want to be able to throw exceptions in dtors: that's the
>only place where I understand them.
You mean in dtors or from dtors? It is the leaking of an exception from
a dtor that causes massive problems.
>
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]