dtors that fail | | |
Can anyone recommend what to do when a dtor fails to perform its task? | | | | re: dtors that fail
* Angel Tsankov:[color=blue]
>
> Can anyone recommend what to do when a dtor fails to perform its task?[/color]
This is a FAQ.
<url: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3>.
--
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? | | | | re: dtors that fail
Angel Tsankov wrote:[color=blue]
> Can anyone recommend what to do when a dtor fails to perform its task?[/color]
(Note, the following is a paraphrasing of Item 8 from Effective C++ by
Scott Meyers.)
If the recommendation in the FAQ annoys you, then you can take the
approach of shifting the responsibility to the user of the class, by
providing a method that does all the things that can throw exceptions.
For example, let's say you have a class to represent a network connection:
class connection
{
socket_type s ;
public :
// other methods here
~connection()
{
try
{
s.close() // this could fail and throw an exception
}
catch (...)
{
// swallow exception, possibly write log entry or such.
}
}
} ;
You might instead shift responsibility to the user of the class by
providing a close method, making your class look like:
class connection
{
socket_type s ;
bool closed ;
public :
// other methods here
void close()
{
s.close() ;
closed = true ;
}
~connection()
{
if (!closed)
{
try
{
s.close() // this could fail and throw an exception
}
catch (...)
{
// swallow exception, possibly write log entry or such.
}
}
}
} ;
Now the user of the class has the option to call close() and deal with
any exceptions that arise, or allow the default behavior of having
exceptions swallowed by the destructor.
-Alan | | | | re: dtors that fail
On Tue, 24 Jan 2006 14:52:33 GMT, alfps@start.no (Alf P. Steinbach)
wrote:[color=blue]
>* Angel Tsankov:[color=green]
>> Can anyone recommend what to do when a dtor fails to perform its task?[/color]
>
>This is a FAQ.
><url: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3>.[/color]
The most important aspect is not mentioned there. Put only code into
the destructor that cannot fail or for which failure is not relevant
for the progress of the program. iostreams are a good example where
this rule is violated. ofstream flushes and closes the file in the
destructor. When close() fails data is probably lost and/or the file
is corrupted.
Best regards,
Roland Pibinger | | | | re: dtors that fail
* Alan Johnson:[color=blue]
> Angel Tsankov wrote:[color=green]
> > Can anyone recommend what to do when a dtor fails to perform its task?[/color]
>
> (Note, the following is a paraphrasing of Item 8 from Effective C++ by
> Scott Meyers.)[/color]
I don't have that book so can't say whether you're paraphrasing it
correctly with emphasis of all that Scott puts emphasis on, but whether
Scott mentions it or not, it's important to know that the technique
sketched below is of _very_ limited utility; I explain why below.
[color=blue]
> If the recommendation in the FAQ annoys you, then you can take the
> approach of shifting the responsibility to the user of the class, by
> providing a method that does all the things that can throw exceptions.
> For example, let's say you have a class to represent a network connection:
>
> class connection
> {
> socket_type s ;
> public :
> // other methods here
>
> ~connection()
> {
> try
> {
> s.close() // this could fail and throw an exception
> }
> catch (...)
> {
> // swallow exception, possibly write log entry or such.
> }
> }
> } ;
>
> You might instead shift responsibility to the user of the class by
> providing a close method, making your class look like:
>
> class connection
> {
> socket_type s ;
> bool closed ;
> public :
> // other methods here
>
> void close()
> {
> s.close() ;
> closed = true ;
> }
>
> ~connection()
> {
> if (!closed)
> {
>
> try
> {
> s.close() // this could fail and throw an exception
> }
> catch (...)
> {
> // swallow exception, possibly write log entry or such.
> }
> }
> }
> } ;
>
> Now the user of the class has the option to call close() and deal with
> any exceptions that arise, or allow the default behavior of having
> exceptions swallowed by the destructor.[/color]
This makes sense for a connection object because it must have a logical
nullstate anyway (the underlying connection can disappear at any time):
for that kind of object it's not just a Good Idea, it's the only natural
way to go about it, and most anything else would be a BUG.
But it's a very Bad Idea, it would be downright Evil, to _introduce_ a
logical nullstate where none is otherwise required.
Because that means you have to check for the logical nullstate in each
and every member function, introducing needless complexity, and if you
forget to check just in one place, then you have a bug. From a less
informal point of view, you're then sacrificing the advantaqe of a class
invariant, which is not a very smart thing to do. Bjarne Stroustrup has
written a few words about how less than smart it is to introduce a
nullstate, or in general, to sacrifice the class invariant, available at
<url: http://public.research.att.com/~bs/3rd_safe0.html>.
--
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? | | | | re: dtors that fail
Alf P. Steinbach wrote:[color=blue]
> This makes sense for a connection object because it must have a logical
> nullstate anyway (the underlying connection can disappear at any time):
> for that kind of object it's not just a Good Idea, it's the only natural
> way to go about it, and most anything else would be a BUG.
>
> But it's a very Bad Idea, it would be downright Evil, to _introduce_ a
> logical nullstate where none is otherwise required.[/color]
What is the alternative? The central question is, "What do I do when my
destructor needs to throw an exception?" The common answer is "Don't
throw exceptions from a destructor, but instead write to a log file or
something." The problem is that that doesn't answer the question. It
just tells you what NOT to do.
A suitably creative person can come up with an example where a
destructor fails, and the program needs to know about it. If I follow
the conventional wisdom then my program does not, in fact, ever learn
about it (unless I do something particularly convoluted by having the
program read from a log file every time an object is destructed).
You gave compelling evidence that the solution proposed in my post is
only applicable to objects with a natural "null state". Is there some
general solution? Or perhaps a design principle that will naturally
lead to destructors that don't need to throw exceptions? Anything for
when "write to a log file" is just not acceptable?
-Alan | | | | re: dtors that fail
Alan Johnson wrote:
[color=blue]
>
> The central question is, "What do I do when my
> destructor needs to throw an exception?"[/color]
You throw an exception. The risk you run is that if the destructor is
being executed as a result of cleanup handling for another exception,
the result will be that the program aborts. But if you need to throw an
exception, you need to throw an exception.
--
Pete Becker
Dinkumware, Ltd. ( http://www.dinkumware.com) | | | | re: dtors that fail
* Alan Johnson:[color=blue]
> Alf P. Steinbach wrote:[color=green]
> > This makes sense for a connection object because it must have a logical
> > nullstate anyway (the underlying connection can disappear at any time):
> > for that kind of object it's not just a Good Idea, it's the only natural
> > way to go about it, and most anything else would be a BUG.
> >
> > But it's a very Bad Idea, it would be downright Evil, to _introduce_ a
> > logical nullstate where none is otherwise required.[/color]
>
> What is the alternative? The central question is, "What do I do when my
> destructor needs to throw an exception?" The common answer is "Don't
> throw exceptions from a destructor, but instead write to a log file or
> something." The problem is that that doesn't answer the question. It
> just tells you what NOT to do.[/color]
The nullstate pattern does not answer that question, either. It's a
device for handling premature disappearance of an external resource,
like a connection or file closed, or window destroyed, and possibly also
for allowing the external resource to be freed before the C++ object is
destroyed. In the event that cleanup fails, whether this is in an
implicit destructor call or an explicit 'close' call, the client code is
facing the same problem: cleanup failed, and this cleanup failure may
occur during stack unwinding, or whatever corresponds to unwinding.
[color=blue]
> A suitably creative person can come up with an example where a
> destructor fails, and the program needs to know about it. If I follow
> the conventional wisdom then my program does not, in fact, ever learn
> about it (unless I do something particularly convoluted by having the
> program read from a log file every time an object is destructed).
>
> You gave compelling evidence that the solution proposed in my post is
> only applicable to objects with a natural "null state".[/color]
Again, note that it really solves a different problem. The problem of
what to do about a cleanup failure is _still there_, in general. E.g.,
void foo()
{
try
{
connection c( something );
// some code here, then
c.close();
}
catch( ... )
{
sayOops();
throw;
}
}
is no different from letting a throwing destructor do the job,
void foo()
{
try
{
connection c( something );
// some code here, then
}
catch( ... )
{
sayOops();
throw;
}
}
[color=blue]
> Is there some general solution?[/color]
As far as I know this is an unsolved problem.
[color=blue]
> Or perhaps a design principle that will naturally
> lead to destructors that don't need to throw exceptions?[/color]
The strong exception guarantee (guaranteeing no exceptions) for all
destructors. But that guarantee can't usually be given when external
resources are involved.
[color=blue]
> Anything for
> when "write to a log file" is just not acceptable?[/color]
Consider what should happen if a connection can't be closed. Perhaps
the user, if there is a user, should be informed and given a choice
between, e.g., continuing with a resource leak (can be risky and
expensive), trying to close the connection again, or aborting; in the
old days we had this as a not uncommon occurence, "Abort (A), Retry (R),
Continue (C)?". If the choice of trying the cleanup again, the retry,
is to be offered then the necessary information to do that needs to be
available, and that seems to imply some user interface things invoked
from the lowest levels. One way to do that is via a callback provided
by some higher level, and one way to avoid doing that is to store the
necessary retry information somewhere, and associate it with the
exception, so that the user (or perhaps other system) interaction can be
handled directly and in-context by the higher level.
Or perhaps the client code is perfectly happy ignoring cleanup failures
in this kind of object. Or perhaps it needs that destructor to call
abort() on failure. So perhaps the class should have a selectable
cleanup failure policy, a functor that is invoked on cleanup failure, or
perhaps such a policy should be global instead of per-class, or perhaps
something in-between -- a global default, overridable per class?
Choices, choices... It doesn't seem as if this is a problem that has a
general solution, only class- and application-specific solutions. But
often the in-practice solution is to abort on cleanup failure, and some
programs (like e.g. some Microsoft programs) then restart themselves,
the Bird Of Phoenix -- we have evolved from ARC to BOP, in 25 years.
Follow-ups set to [comp.programming], since it seems we have exhausted
the C++ content of the question and are over in general design.
--
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? | | | | re: dtors that fail
"Roland Pibinger" <rpbg123@yahoo.com> wrote in message
news:43d68757.2302828@news.utanet.at...[color=blue]
> On Tue, 24 Jan 2006 14:52:33 GMT, alfps@start.no (Alf P. Steinbach)
> wrote:[color=green]
>>* Angel Tsankov:[color=darkred]
>>> Can anyone recommend what to do when a dtor fails to perform its
>>> task?[/color]
>>
>>This is a FAQ.
>><url:
>>http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3>.[/color]
>
> The most important aspect is not mentioned there. Put only code into
> the destructor that cannot fail or for which failure is not relevant
> for the progress of the program.[/color]
How can one abide by this rule all the time? | | | | re: dtors that fail
On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
<fn42551@fmi.uni-sofia.bg> wrote:[color=blue]
>"Roland Pibinger" <rpbg123@yahoo.com> wrote in message
>news:43d68757.2302828@news.utanet.at...[color=green]
>> On Tue, 24 Jan 2006 14:52:33 GMT, alfps@start.no (Alf P. Steinbach)
>> wrote:[color=darkred]
>>>* Angel Tsankov:
>>>> Can anyone recommend what to do when a dtor fails to perform its
>>>> task?
>>>
>>>This is a FAQ.
>>><url:
>>>http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3>.[/color]
>>
>> The most important aspect is not mentioned there. Put only code into
>> the destructor that cannot fail or for which failure is not relevant
>> for the progress of the program.[/color]
>
>How can one abide by this rule all the time?[/color]
This is more a design than an implementation question. When you really
need it, one promising strategy is 'transactional' programming (akin
to database programming) with acquire(), commit() and rollback()
functions. The destructor always performs a rollback() unless a
successful commit() by the user has happend before. It's much easier
to implement a non-failing (or failure-tolerant) rollback-function
than an appropriate commit-function.
Best wishes,
Roland Pibinger | | | | re: dtors that fail
"Roland Pibinger" <rpbg123@yahoo.com> wrote in message
news:43d6b85c.1874571@news.utanet.at...[color=blue]
> On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
> <fn42551@fmi.uni-sofia.bg> wrote:[color=green]
>>"Roland Pibinger" <rpbg123@yahoo.com> wrote in message
>>news:43d68757.2302828@news.utanet.at...[color=darkred]
>>> On Tue, 24 Jan 2006 14:52:33 GMT, alfps@start.no (Alf P.
>>> Steinbach)
>>> wrote:
>>>>* Angel Tsankov:
>>>>> Can anyone recommend what to do when a dtor fails to perform its
>>>>> task?
>>>>
>>>>This is a FAQ.
>>>><url:
>>>>http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3>.
>>>
>>> The most important aspect is not mentioned there. Put only code
>>> into
>>> the destructor that cannot fail or for which failure is not
>>> relevant
>>> for the progress of the program.[/color]
>>
>>How can one abide by this rule all the time?[/color]
>
> This is more a design than an implementation question. When you
> really
> need it, one promising strategy is 'transactional' programming (akin
> to database programming) with acquire(), commit() and rollback()
> functions. The destructor always performs a rollback() unless a
> successful commit() by the user has happend before. It's much easier
> to implement a non-failing (or failure-tolerant) rollback-function
> than an appropriate commit-function.[/color]
This is fine. Really. But rollback can fail, too. So, what should be
done when rollback fails in a dtor? | | | | re: dtors that fail
On Wed, 25 Jan 2006 10:10:39 +0200, "Angel Tsankov"
<fn42551@fmi.uni-sofia.bg> wrote:[color=blue]
>"Roland Pibinger" <rpbg123@yahoo.com> wrote in message
>news:43d6b85c.1874571@news.utanet.at...[color=green]
>> On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
>> <fn42551@fmi.uni-sofia.bg> wrote:[color=darkred]
>>>How can one abide by this rule all the time?[/color]
>>
>> This is more a design than an implementation question. When you
>> really
>> need it, one promising strategy is 'transactional' programming (akin
>> to database programming) with acquire(), commit() and rollback()
>> functions. The destructor always performs a rollback() unless a
>> successful commit() by the user has happend before. It's much easier
>> to implement a non-failing (or failure-tolerant) rollback-function
>> than an appropriate commit-function.[/color]
>
>This is fine. Really. But rollback can fail, too. So, what should be
>done when rollback fails in a dtor?[/color]
Ok, here is a concrete example for 'transactional' programming, a
FileWriter class remotely resembling ofstream. The implementation is
only sketched.
class FileWriter {
public:
FileWriter(): tmpFile(0), isCommitted(false){}
~FileWriter() {
if (!isCommitted) {
rollback();
}
}
bool open (const char* fileName) {
// open a temporary(!) file in the same directory as 'fileName'
tmpFile = fopen(tempFileName, "w" );
// error handling
}
bool write (const char *buf, size_t size) {...}
bool commit() {
if (tmpFile && fclose (tmpFile) == 0) {
if (rename (tempFileName, fileName) == 0) {
isCommitted = true;
tmpFile = 0;
}
}
if (!isCommitted) {
rollback();
}
return isCommitted == true;
}
void rollback() {
if (tmpFile) {
fclose (tmpFile);
remove (tmpFile);
}
isCommitted = false;
tmpFile = 0;
}
private:
File* tmpFile;
bool isCommitted;
FileWriter (const FileWriter&);
FileWriter& operator= (const FileWriter&);
};
// usage:
bool foo() {
FileWriter fileWriter;
if (fileWriter.open ("outFile")) {
while (...) {
bool success = fileWriter.wite (...);
// ...
}
// ...
}
return fileWriter.commit();
}
The above code is not only exception safe but also crash safe. In the
worst case a corrupted temporary file remains. Things become slightly
more complicated when a file with the same name already exists.
Note that writing to and reading from a file are not symmetric. For a
corresponding 'FileReader' class it would be ok to ignore the return
value of fclose (and not use transactional programming) because the
file is opend 'read only'.
Best wishes,
Roland Pibinger |  | | | | /bytes/about
We are a network of experts and professionals in IT and software development that help one another with answers to tough questions and share insights.
Get the best answers to your questions from over 226,467 network members.
|