Exceptions in destructors

R

Rennie deGraaf

I know that if an exception is thrown from a destructor while unwinding
the stack because of another exception, terminate() is called (FAQ
17.3). How exactly does this rule work? Is it acceptable to both throw
/and/ catch an exception inside a destructor, as in the following code?

struct Foo
{
void finalize()
{
throw 1.0;
}
~Foo()
{
try
{
finalize();
}
catch (double d)
{}
}
};

void bar()
{
Foo f;
throw 1;
}

int main()
{
try
{
bar();
}
catch (int i)
{}
return 0;
}

Rennie deGraaf


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)

iD8DBQFGNVl6IvU5mZP08HERAnkaAJ9rODkY55YL/ON+PXYS0ImI2K783QCgzAwr
6zQzt0Hkj6h8KYRFfI/xYw8=
=H9qr
-----END PGP SIGNATURE-----
 
R

red floyd

Rennie said:
I know that if an exception is thrown from a destructor while unwinding
the stack because of another exception, terminate() is called (FAQ
17.3). How exactly does this rule work? Is it acceptable to both throw
/and/ catch an exception inside a destructor, as in the following code?

[code redacted]

Yes, the restriction is that the exception may not escape the destructor.
 
G

Greg Herlihy

I know that if an exception is thrown from a destructor while unwinding
the stack because of another exception, terminate() is called (FAQ
17.3). How exactly does this rule work? Is it acceptable to both throw
/and/ catch an exception inside a destructor, as in the following code?

struct Foo
{
void finalize()
{
throw 1.0;
}
~Foo()
{
try
{
finalize();
}
catch (double d)
{}
}
};

The runtime calls terminate() if - during exception processing - another
thrown exception "escapes" from the destructor in which it was thrown.

So as long as the thrown exception in the code above is caught before Foo's
destructor exits, then terminate() will not be called. Therefore the above
code is technically safe - but it is also fragile. Specifically, Foo's
destructor assumes that finalize() will only ever throw a double - but
through there is nothing in finalize()'s implementation that enforces such a
restriction - nor is there anything in Foo's destructor to check whether its
assumption about the type of finalize()'s thrown exceptions - remains valid
since the last time it checked.

Greg
 
R

Rennie deGraaf

Greg said:
The runtime calls terminate() if - during exception processing - another
thrown exception "escapes" from the destructor in which it was thrown.

So as long as the thrown exception in the code above is caught before Foo's
destructor exits, then terminate() will not be called. Therefore the above
code is technically safe - but it is also fragile. Specifically, Foo's
destructor assumes that finalize() will only ever throw a double - but
through there is nothing in finalize()'s implementation that enforces such a
restriction - nor is there anything in Foo's destructor to check whether its
assumption about the type of finalize()'s thrown exceptions - remains valid
since the last time it checked.

Greg

In other words, I really should add throw specifiers, like this:

struct Foo
{
void finalize() throw(double)
{
throw 1.0;
}
~Foo() throw()
{
try
{
finalize();
}
catch (double d)
{}
}
};

I suppose that it would generally be a good idea to /always/ tag my
destructors with throw() unless I have a good reason to do otherwise?
For that matter, are there any circumstances where it would be safe to
throw an exception from a destructor?

Rennie deGraaf


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)

iD8DBQFGNX7tIvU5mZP08HERAu/9AJ0R26LkBLs6hVMjMAVsH181cMjTtgCfa7p2
jCaJy2cRDZA0JQgMQbdslt0=
=u7Mx
-----END PGP SIGNATURE-----
 
G

Greg Herlihy

In other words, I really should add throw specifiers, like this:

struct Foo
{
void finalize() throw(double)
{
throw 1.0;
}
~Foo() throw()
{
try
{
finalize();
}
catch (double d)
{}
}

};

I suppose that it would generally be a good idea to /always/ tag my
destructors with throw() unless I have a good reason to do otherwise?

Personally, I would not bother adding throw() to destructor
declarations. The time and effort for making such a change would
probably be better spent making sure that no destructor in the program
propagtes exceptions beyond its scope.

On the other hand, the exception specification for finalize() I would
keep. Even though C++ exception specifications are checked at runtime
and not compile-time - at least the double exception specification
serves to document a dependency on finalize()'s thrown exception type
- that previously was not documented anywhere.

For that matter, are there any circumstances where it would be safe to
throw an exception from a destructor?

Sure - in fact I would imagine that under most circumstances throwing
an exception from a destructor would be safe. The problem is that
there are always circumstances when throwing an exception from a
destructor is not safe - so what should the program do instead?

In other words, it does not make a lot of sense for a program to throw
an exception - unless it is safe to throw that exception every time
the program wants to throw it. Otherwise, the code paths of the
program need to split into two different error-handling paths - one
when the exception is thrown and another when it is not. So throwing
an exception from a destructor does not end up simplifying or
consolidating the program's error handling - instead the two execution
paths just makes the program more complicated for no reason.

Greg
 
J

James Kanze

On Apr 30, 7:30 am, Rennie deGraaf <[email protected]
pork.ucalgary.ca> wrote:

[...]
In other words, I really should add throw specifiers, like this:
struct Foo
{
void finalize() throw(double)
{
throw 1.0;
}
~Foo() throw()
{
try
{
finalize();
}
catch (double d)
{}
}
};
I suppose that it would generally be a good idea to /always/ tag my
destructors with throw() unless I have a good reason to do otherwise?
For that matter, are there any circumstances where it would be safe to
throw an exception from a destructor?

Opinions with regards to the utility of exception specifiers
vary:). In general, there's pretty much a consensus that
specifiers which list possible exceptions are useless; the only
possible effect they can have is to result in slower code.
There's much less consensus regarding empty specifiers, e.g.
throw(); knowing that a function cannot throw is often important
information, both to the user and to the optimizer.

In practice, with regards to the reader, I would generally
expect a destructor not to throw, and any other function to
throw std::exception, or anything derived from it, unless
otherwise documented. Throwing from a destructor should be so
exceptional that not just the possibility itself, but the
justification and reasons behind it should be documented; an
exception specifier is not sufficient for this. And because
non-throwing destructors are so common, you can certainly not
count on the absense of a specifier on the destructor to signal
that it might throw. So from the human reader's point of view,
an exception specifier on a destructor doesn't really add any
useful information. Just the reverse is true for other
functions, however; in the absense of other documentation, you
should assume that the function can throw. And a simple
"throw()" is excellent documentation that it cannot. Unlike the
case for destructors, I don't think you normally have to justify
why the function doesn't throw; it's sufficient to document
clearly that it doesn't.
 

Ask a Question

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

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

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top