Requirements for exception classes

K

Kevin Goodsell

If I'm writing a class that will be used as an exception, what kinds of
things do I need to watch out for? For example, is it necessary to make
sure that the members of the class don't throw?

Suppose my class has one or more std::string members. These could throw
bad_alloc on construction or copy. Is this dangerous? Here's an example:

#include <string>

class SimpleException
{
std::string text;
public:
SimpleException(const std::string &s) : text(s) {}
const std::string &GetText() { return text; }
};

int main()
{
try {
throw SimpleException("BORKED!"); /* what if constructor throws? */
}
catch (SimpleException e) { /* what if copying throws? */
// ...
}

return 0;
}

Thanks.

-Kevin
 
A

Alexander Terekhov

Kevin said:
If I'm writing a class that will be used as an exception, what kinds of
things do I need to watch out for? For example, is it necessary to make
sure that the members of the class don't throw?

Suppose my class has one or more std::string members.

Bad idea.
These could throw bad_alloc on construction or copy.

Write your own, immutable, ref-counted (or something like that) thing.
Beside that, you don't really need what() in any sensible application.
Is this dangerous?

Yes. Make your constructors no-throw.

regards,
alexander.
 
G

Gianni Mariani

Kevin said:
If I'm writing a class that will be used as an exception, what kinds of
things do I need to watch out for? For example, is it necessary to make
sure that the members of the class don't throw?

An exception that throws is probably a double exception and innevitably
an abort.
Suppose my class has one or more std::string members. These could throw
bad_alloc on construction or copy. Is this dangerous? Here's an example:

Well sure if you catch bad_alloc and throw a std::string, almost
inevitably a Murphy's law candidate.
#include <string>

class SimpleException
{
std::string text;
public:
SimpleException(const std::string &s) : text(s) {}
const std::string &GetText() { return text; }
};

int main()
{
try {
throw SimpleException("BORKED!"); /* what if constructor throws? */
}
catch (SimpleException e) { /* what if copying throws? */


I read once that catching references was a better way because it could
deal with polymorphic exceptions.


catch (const SimpleException & e) {


Since I rarely use exceptions, I'd have to do more research but it is
somthing you could consider.
 
K

Kevin Goodsell

Chris said:
Not in g++ 3.2.3 at least (don't know what the standard says). I tried:

<code snipped>

This is a good example of one of my "theories" that I am hoping to
either prove or disprove. In the case of the initial construction of the
exception object (at the 'throw' expression), no exception has been
thrown yet. So if that constructor throws, I *think* the exception from
the constructor should propagate instead of the original exception.
Therefore it would be OK to throw from that constructor.

As for copying of the exception object, I suspect a throw in that case
would be bad. But the stuff I found in TC++PLS basically only said that
an exception thrown *from a destructor* that is invoked as a result of
"stack unwinding" during an exception causes terminate() to be called.

I've been looking for more information, but haven't found anything good yet.

-Kevin
 
G

Gianni Mariani

Chris said:
Not in g++ 3.2.3 at least (don't know what the standard says). I tried:

#include<iostream>

using namespace std;

class ding
{ public:
ding(){cout << "ding constr." << endl; throw 5;}
};

class test
{ public:
test(){cout << "test constr." << endl; throw ding();}
};

int main()
{ try
{ test t;
} catch(int)
{ cout << "cought int" << endl;
} catch(ding)
{ cout << "cought ding" << endl;
}
return 0;
}

output:

test constr.
ding constr.
cought int

This same code dumps on gcc 3.3.1

test constr.
ding constr.
Abort (core dumped)


#0 0x42029241 in kill () from /lib/i686/libc.so.6
#1 0x4202902a in raise () from /lib/i686/libc.so.6
#2 0x4202a7d2 in abort () from /lib/i686/libc.so.6
#3 0x4009b65c in __cxxabiv1::__terminate(void (*)())
(handler=0x4202a664 <abort>) at
.../../../../gcc-3.3.1/libstdc++-v3/libsupc++/eh_terminate.cc:47
#4 0x4009b55e in __gxx_personality_v0 (version=1, actions=6,
exception_class=5138137972254386944, ue_header=0x0, context=0xbffff5e0)
at ../../../../gcc-3.3.1/libstdc++-v3/libsupc++/eh_personality.cc:434
#5 0x40107cd4 in _Unwind_RaiseException_Phase2 (exc=0x8049fc8,
context=0xbffff5e0) at ../../gcc-3.3.1/gcc/unwind.inc:57
#6 0x40107e0b in _Unwind_RaiseException (exc=0x8049fc8) at
.../../gcc-3.3.1/gcc/unwind.inc:127
#7 0x4009b7cc in __cxa_throw (obj=0x8049fc8, tinfo=0x400c3e44,
dest=0x400c3e44 <globals_static>) at
.../../../../gcc-3.3.1/libstdc++-v3/libsupc++/eh_throw.cc:75
#8 0x08048b78 in ding (this=0x8049f90) at zzz.cpp:8
#9 0x08048afd in test (this=0xbffff75f) at zzz.cpp:13
#10 0x08048922 in main () at zzz.cpp:18
 
C

Chris Dams

An exception that throws is probably a double exception and innevitably
an abort.

Not in g++ 3.2.3 at least (don't know what the standard says). I tried:

#include<iostream>

using namespace std;

class ding
{ public:
ding(){cout << "ding constr." << endl; throw 5;}
};

class test
{ public:
test(){cout << "test constr." << endl; throw ding();}
};

int main()
{ try
{ test t;
} catch(int)
{ cout << "cought int" << endl;
} catch(ding)
{ cout << "cought ding" << endl;
}
return 0;
}

output:

test constr.
ding constr.
cought int

Bye,
Chris Dams
 
K

Kevin Goodsell

Gianni said:
This same code dumps on gcc 3.3.1

test constr.
ding constr.
Abort (core dumped)

Then either gcc 3.3.1 is wrong or I'm not correctly interpreting the
standard.

15.5.1 The terminate() function [except.terminate]

1 In the following situations exception handling must be abandoned for
less subtle error handling techniques:

--when the exception handling mechanism, after completing evaluation
of the expression to be thrown but before the exception is caught
(_except.throw_), calls a user function that exits via an uncaught
exception,[134]

<other cases snipped>

2 In such cases,
void terminate();
is called (_lib.exception.terminate_). In the situation where no
matching handler is found, it is implementation-defined whether or not
the stack is unwound before terminate() is called. In all other situ-
ations, the stack shall not be unwound before terminate() is called.
An implementation is not permitted to finish stack unwinding prema-
turely based on a determination that the unwind process will eventu-
ally cause a call to terminate().

Footnote 134) reads:

134) For example, if the object being thrown is of a class with a copy
constructor, terminate() will be called if that copy constructor exits
with an exception during a throw.

The key part being "*after* completing evaluation of the expression to
be thrown but *before* the exception is caught".

-Kevin
 
G

Gianni Mariani

....nippity snip
Then either gcc 3.3.1 is wrong or I'm not correctly interpreting the
standard.

.... standard snipped
The key part being "*after* completing evaluation of the expression to
be thrown but *before* the exception is caught".

I think I read the standard in a way that makes gcc 3.3.1 correct.

i.e. The expression IS evaluated - it threw an exception so we're
talking *after* and no exception is ever caught in this code so we're
talking *before* and the doc says teminate should be called.

What do you think ?
 
K

Kevin Goodsell

Gianni said:
... standard snipped



I think I read the standard in a way that makes gcc 3.3.1 correct.

i.e. The expression IS evaluated - it threw an exception so we're
talking *after* and no exception is ever caught in this code so we're
talking *before* and the doc says teminate should be called.

What do you think ?

I'm not following your logic. The expression to be thrown is the thing
that appears after 'throw'. In the case of

throw Foo();

The expression to be thrown is Foo(), and its evaluation is not complete
until the constructor finishes. Are you saying that it is completed by
throwing its own exception? I would have to disagree with that.

I looked through GCC's bugzilla and the DRs from WG21's web site to see
if I could find an explanation for GCC's handling of that code, but I
didn't find anything. I think it may be a bug that either hasn't been
reported or that I overlooked.

-Kevin
 
G

Gianni Mariani

Kevin said:
I'm not following your logic. The expression to be thrown is the thing
that appears after 'throw'. In the case of

throw Foo();

The expression to be thrown is Foo(), and its evaluation is not complete
until the constructor finishes. Are you saying that it is completed by
throwing its own exception? I would have to disagree with that.

I looked through GCC's bugzilla and the DRs from WG21's web site to see
if I could find an explanation for GCC's handling of that code, but I
didn't find anything. I think it may be a bug that either hasn't been
reported or that I overlooked.


It all depends on what "complete" means in this context.

a) Complete could mean that execution of the expression has ended. (if
it failed or not may not be relevant) In the sense that no further
expression processing needs to be made.

b) Complete in the sense that there were no exceptions in computing the
expression and the expression computation successfully finished.

I tend to believe that it is a). However, since there is ambiguity and
a standard with unintentional ambiguities is an error, I think this is a
candidate for a defect report.

I bags you write one since you brought it up ! :)
 

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,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top