473,386 Members | 1,821 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

dtors that fail

Can anyone recommend what to do when a dtor fails to perform its task?
Jan 24 '06 #1
11 1966
* 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>.

--
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?
Jan 24 '06 #2
Angel Tsankov wrote:
Can anyone recommend what to do when a dtor fails to perform its task?


(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
Jan 24 '06 #3
On Tue, 24 Jan 2006 14:52:33 GMT, al***@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. 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
Jan 24 '06 #4
* Alan Johnson:
Angel Tsankov wrote:
Can anyone recommend what to do when a dtor fails to perform its task?
(Note, the following is a paraphrasing of Item 8 from Effective C++ by
Scott Meyers.)


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.

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.


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?
Jan 24 '06 #5
Alf P. Steinbach wrote:
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.


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
Jan 24 '06 #6
Alan Johnson wrote:

The central question is, "What do I do when my
destructor needs to throw an exception?"


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)
Jan 24 '06 #7
* Alan Johnson:
Alf P. Steinbach wrote:
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.
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.


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.

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".
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;
}
}

Is there some general solution?
As far as I know this is an unsolved problem.

Or perhaps a design principle that will naturally
lead to destructors that don't need to throw exceptions?
The strong exception guarantee (guaranteeing no exceptions) for all
destructors. But that guarantee can't usually be given when external
resources are involved.

Anything for
when "write to a log file" is just not acceptable?


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?
Jan 24 '06 #8

"Roland Pibinger" <rp*****@yahoo.com> wrote in message
news:43**************@news.utanet.at...
On Tue, 24 Jan 2006 14:52:33 GMT, al***@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.


How can one abide by this rule all the time?

Jan 24 '06 #9
On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
<fn*****@fmi.uni-sofia.bg> wrote:
"Roland Pibinger" <rp*****@yahoo.com> wrote in message
news:43**************@news.utanet.at...
On Tue, 24 Jan 2006 14:52:33 GMT, al***@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.


How can one abide by this rule all the time?


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
Jan 24 '06 #10

"Roland Pibinger" <rp*****@yahoo.com> wrote in message
news:43**************@news.utanet.at...
On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
<fn*****@fmi.uni-sofia.bg> wrote:
"Roland Pibinger" <rp*****@yahoo.com> wrote in message
news:43**************@news.utanet.at...
On Tue, 24 Jan 2006 14:52:33 GMT, al***@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.


How can one abide by this rule all the time?


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.


This is fine. Really. But rollback can fail, too. So, what should be
done when rollback fails in a dtor?

Jan 25 '06 #11
On Wed, 25 Jan 2006 10:10:39 +0200, "Angel Tsankov"
<fn*****@fmi.uni-sofia.bg> wrote:
"Roland Pibinger" <rp*****@yahoo.com> wrote in message
news:43**************@news.utanet.at...
On Wed, 25 Jan 2006 00:53:10 +0200, "Angel Tsankov"
<fn*****@fmi.uni-sofia.bg> wrote:
How can one abide by this rule all the time?


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.


This is fine. Really. But rollback can fail, too. So, what should be
done when rollback fails in a dtor?


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
Jan 25 '06 #12

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

8
by: Gerhard Wolfstieg | last post by:
The following situation: class A0 { static A0 *a0; // something like this to publish the pointer public: A0() { a0 = this; } virtual ~A0(){}
6
by: BigMan | last post by:
Is it safe to call nonvirtual member functions from ctors and dtors? What about virtual ones?
3
by: Wenjie | last post by:
Hello, I have a question concerning style related to conditional decision: #define FAIL -1 int f(); if (f() != FAIL) or better if ( FAIL != f())? Why?
27
by: Chess Saurus | last post by:
I'm getting a little bit tired of writing if (a = malloc(...) == NULL) { // error code } I mean, is it really possible that a malloc call could fail, except in the case of running out of...
1
by: xxxxyz | last post by:
Hi, I want to create a class with a constructor which can fail (for example if (..) fail;). When constructor fail I want the variable to point to null. Example: class PosInt{ int i; public...
1
by: Robbie Hatley | last post by:
Say I have an ifstream object, like so: #include <iostream> #include <fstream> int main(int, char* Sam) { std::ifstream Bob; Bob.open(Sam); std::string buffer; while (42)
34
by: niranjan.singh | last post by:
This is regarding to test an SDK memory stuff. In what situation malloc gets fail. any comment/reply pls.... regards
5
by: marshmallowww | last post by:
I have an Access 2000 mde application which uses ADO and pass through queries to communicate with SQL Server 7, 2000 or 2005. Some of my customers, especially those with SQL Server 2005, have had...
21
by: Chris M. Thomasson | last post by:
Is it every appropriate to throw in a dtor? I am thinking about a simple example of a wrapper around a POSIX file... ________________________________________________________________________ class...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.