exceptions in diamond's destructors

V

vgrebinski

Hi everyone,
Suppose we have a diamond shaped diagram:

A
/ \
B1 B2
\ /
C

and assume that C' constructor invokes

A, B1, B2, C

C's destructors then are called in the reverse:

C, B2, B1, A

What is SUPPOSED to hapen if ~B2() throws an exception?
I would think, that ~B1() and ~A() should be called to finish the
destruction of C (just like in case if we were constructing C and there
was an exception in B2's constructor), however, some popular compilers
give different results (below is the sequence of destructors called to
destroy C with an exception thrown in B2):

gcc-3.3 and up: C, B2, B1, A // seems to be OK
Intel icc9: C, B2, A, B1 , A // ~A() called twice!
MS VC6/2005Express: C, B2, B1 // where is ~A() ?

So, what standard says about this situation? Is it undefined or gcc is
right?

Regards,
Vladimir

Below is a short program to demonstrate the behavior:
///////////////////////////////////////////////////////////////
#include <iostream>

using namespace std;
struct A {~A() {cout << "A" <<endl;}};
struct B1 : public virtual A {~B1() {cout << "B1" <<endl;}};
struct B2 : public virtual A {
~B2() {
cout << "B2" <<endl;
throw "bad things happened";
}
};
struct C : public B1, public B2 {~C() {cout << "C" <<endl;}};
int main(int argc, char* argv[])
{
try {
C * p = new C;
delete p;
} catch (...) {};
return 0;
}

//////////////////////////////////
 
M

Mike Wahler

Hi everyone,
Suppose we have a diamond shaped diagram:

A
/ \
B1 B2
\ /
C

and assume that C' constructor invokes

A, B1, B2, C

C's destructors then are called in the reverse:

C, B2, B1, A

What is SUPPOSED to hapen if ~B2() throws an exception?
I would think, that ~B1() and ~A() should be called to finish the
destruction of C (just like in case if we were constructing C and there
was an exception in B2's constructor), however, some popular compilers
give different results (below is the sequence of destructors called to
destroy C with an exception thrown in B2):

gcc-3.3 and up: C, B2, B1, A // seems to be OK
Intel icc9: C, B2, A, B1 , A // ~A() called twice!
MS VC6/2005Express: C, B2, B1 // where is ~A() ?

So, what standard says about this situation? Is it undefined or gcc is
right?

I'm not sure what the standard says about throwing an exception
from a destructor, but every 'how to write C++ code' book I've
read addresses the issue like so: Don't Do That. And IMO combining
this questionable practice with the already sticky issues of 'diamond
inheritance' will only further decrease code coherence.

I think the better issue to discuss is why you might feel
you *need* a destructor to throw. IOW look at your *design*.

But that's just my opinion. :) I'll let the true gurus explain
the technical details.

-Mike
 
V

vgrebinski

My true reason to ask is to prove superiority of gcc, of course :)
Jokes aside, I came accross it by curiosity and got even more
interested when the compilers I have access to disagreed on the answer.
On the other hand, I can see it happens in practice if you call other
functions inside a destructor and one of them throws.
- Vladimir
 
J

Josh Mcfarlane

My true reason to ask is to prove superiority of gcc, of course :)
Jokes aside, I came accross it by curiosity and got even more
interested when the compilers I have access to disagreed on the answer.
On the other hand, I can see it happens in practice if you call other
functions inside a destructor and one of them throws.
- Vladimir

That's just it though - Destructors should never have the chance to
throw. If something else has already thrown an exception, and the
current object is being destructed, another exception, and you're dead.
 
N

Niels Dybdahl

I think the better issue to discuss is why you might feel
you *need* a destructor to throw. IOW look at your *design*.

If you use RAII also for handling hardware (files, communication
channels...), then it might fail while you are closing and in that case I
would like to throw. If I remember correctly, there is a function that you
can call to determine, if you are already unwinding the stack due to another
throw. Is that function part of the standard or implementation specific ?

But in any case the standard should ensure that objects going out of scope
should be destroyed, so it seems gcc is the only one getting it right.

Niels Dybdahl
 
P

peter koch

Niels Dybdahl skrev:
If you use RAII also for handling hardware (files, communication
channels...), then it might fail while you are closing and in that case I
would like to throw. If I remember correctly, there is a function that you
can call to determine, if you are already unwinding the stack due to another
throw. Is that function part of the standard or implementation specific ?

There is (it is named uncaught_exception if my memory is correct), but
is it really such a good idea to use it? You now have two ways of
specifying an error which leads to extra trouble. Also, if you can't
release a resource (as is the case in the problem we are discussing),
something is seriously wrong and it might be a better idea to terminate
the program.

/Peter
 
N

Niels Dybdahl

I think the better issue to discuss is why you might feel
?

There is (it is named uncaught_exception if my memory is correct), but
is it really such a good idea to use it? You now have two ways of
specifying an error which leads to extra trouble. Also, if you can't
release a resource (as is the case in the problem we are discussing),
something is seriously wrong and it might be a better idea to terminate
the program.

It is not nice to close a GUI application, just because a hardware error
occurs.
F.ex if the application is writing a document to a removable disk and the
disk is removed just before the destructor closes the file, you can not be
sure that the document is properly written. You should allow the user to
save the document somewhere else before closing the application.

Once I was using this approach and checked uncaught_exception to find out if
it was safe to throw an exception, but it turned out that I could not trust
uncaught_exception in MSVC6, so I had to give it up. So do not use
destructors to flush data or free resources if it might fail. There are
simply too many bad implementations.

Niels Dybdahl
 
D

deane_gavin

My true reason to ask is to prove superiority of gcc, of course :)
Jokes aside, I came accross it by curiosity and got even more
interested when the compilers I have access to disagreed on the answer.
On the other hand, I can see it happens in practice if you call other
functions inside a destructor and one of them throws.
- Vladimir

To add to some of the responses from others, this subject is discussed
in the FAQ

http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3

Gavin Deane
 
V

vgrebinski

Okay, let's not mix definition of the language and best practices. If
throwing exception inside a destructor is legal, then standard should
one way or another describe what will happen with yet undestroyed
subclasses. That is my original question and that is what I want to
understand.

Another thing is that you don't need a diamond diagram: VC2005 doesn't
call base class destructor even if exception is thrown in ~B() for
A<-B<-C diagram (~A is not called).

Regards,
Vladimir
 
V

vgrebinski

Okay, let's not mix definition of the language and best practices. If
throwing exception inside a destructor is legal, then standard should
one way or another describe what will happen with yet undestroyed
subclasses. That is my original question and that is what I want to
understand.

Another thing is that you don't need a diamond diagram: VC2005 doesn't
call base class destructor even if exception is thrown in ~B() for
A<-B<-C diagram (~A is not called).

Regards,
Vladimir
 
A

Alf P. Steinbach

* (e-mail address removed):
Hi everyone,
Suppose we have a diamond shaped diagram:

A
/ \
B1 B2
\ /
C

and assume that C' constructor invokes

A, B1, B2, C

C's destructors then are called in the reverse:

C, B2, B1, A

What is SUPPOSED to hapen if ~B2() throws an exception?

15.2/2, emphasis added:
"An object that is partially constructed or _partially destroyed_ will
have destructors executed for all of its fully constructed subobjects".

However, the implicit context here, from 15.2/1, is destruction of
automatic objects due to an exception.

Your example program use dynamic allocation.

I would think, that ~B1() and ~A() should be called to finish the
destruction of C (just like in case if we were constructing C and there
was an exception in B2's constructor)

Yep, I would also think that.

, however, some popular compilers
give different results (below is the sequence of destructors called to
destroy C with an exception thrown in B2):

gcc-3.3 and up: C, B2, B1, A // seems to be OK
Intel icc9: C, B2, A, B1 , A // ~A() called twice!
MS VC6/2005Express: C, B2, B1 // where is ~A() ?

So, what standard says about this situation? Is it undefined or gcc is
right?

The way I read the probable _intention_ of the standard g++ is right,
but the standard could be more clear...
 
S

Shezan Baig

Okay, let's not mix definition of the language and best practices. If
throwing exception inside a destructor is legal, then standard should
one way or another describe what will happen with yet undestroyed
subclasses.


The standard already describes this (15.2.2):

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.

Another thing is that you don't need a diamond diagram: VC2005 doesn't
call base class destructor even if exception is thrown in ~B() for
A<-B<-C diagram (~A is not called).


I think this falls under the category quoted above (see 10.2 for a
confirmation of this). So if ~A() is not executed, then the compiler
is broken.

Hope this helps,
-shez-
 
G

Greg Comeau

But in any case the standard should ensure that objects going out of scope
should be destroyed, so it seems gcc is the only one getting it right.

I may have lost the context, but what is it that only hcc gets right?
And right according to what?
 
P

peter koch

Niels Dybdahl skrev:
It is not nice to close a GUI application, just because a hardware error
occurs.
F.ex if the application is writing a document to a removable disk and the
disk is removed just before the destructor closes the file, you can not be
sure that the document is properly written. You should allow the user to
save the document somewhere else before closing the application.

In that case, your destructor does to much. You should presumably have
some logic to flush the file (e.g. doing FlushFileBuffers if you
program in Windows). The code to do this stuff does not look as if
belongs in a destructor but in a separate piece of code.
Once I was using this approach and checked uncaught_exception to find out if
it was safe to throw an exception, but it turned out that I could not trust
uncaught_exception in MSVC6, so I had to give it up. So do not use
destructors to flush data or free resources if it might fail. There are
simply too many bad implementations.
I believe this is still the case although i'm not sure about 8.0. I
tried to use uncaught_exception for tracing purposes but as you had to
give up with VC++ 6.0.

/Peter
 
N

Niels Dybdahl

But in any case the standard should ensure that objects going out of
scope
I may have lost the context, but what is it that only hcc gets right?
And right according to what?

According to the tests performed by the original poster, only gcc cleans
properly when an exception is thrown in a destructor.
Intels compiler destroyed one object twice afterwards and Microsofts forgot
to destroy one of the objects.
In addition you can not rely upon that Microsofts compiler sets
uncaught_exception correctly.

Niels Dybdahl
 
N

Niels Dybdahl

I think the better issue to discuss is why you might feel
In that case, your destructor does to much. You should presumably have
some logic to flush the file (e.g. doing FlushFileBuffers if you
program in Windows). The code to do this stuff does not look as if
belongs in a destructor but in a separate piece of code.

The only reason I can see not to put flushing and closing commands, that
might throw, into a destructor, is the flaws in the compilers. But that
reason is enough not to do it.

Niels Dybdahl
 
G

Greg Comeau

According to the tests performed by the original poster, only gcc cleans
properly when an exception is thrown in a destructor.
Intels compiler destroyed one object twice afterwards and Microsofts forgot
to destroy one of the objects.
In addition you can not rely upon that Microsofts compiler sets
uncaught_exception correctly.

Ok, that sound about right. FYI, Comeau also provides C, B2, B1, A
which appears to be correct, though the advise "don't do that"
is reasonable.
 
P

peter koch

Niels Dybdahl skrev:
The only reason I can see not to put flushing and closing commands, that
might throw, into a destructor, is the flaws in the compilers. But that
reason is enough not to do it.

Well.... i doubt that a reasonably written destructor could do what is
required in case of a disk error. This requires user intervention and
quite complex code which should not be put in a destructor (among other
reasons because destructors should not throw).

/Peter
 

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

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top