Templated throw() specifications.

J

jason.cipriani

Consider this program, which defines a template class who's template
parameter is the type of an exception that can be thrown by members of
the class:

=== BEGIN EXAMPLE ===

#include <iostream>
#include <string>
using namespace std;

void function () throw (string) {
throw string("Oops");
}

template <class EX> class A {
public:
typedef void (* FN) ();
explicit A (FN f) : f_(f) { }
void Call () throw (EX) { f_(); }
private:
FN f_;
};

int main () {
A<string> a(&function);
try {
a.Call();
} catch (const string &s) {
cout << s << endl;
}
}

=== END EXAMPLE ===

The goal of the above is to allow the callback function to throw any
exception it wants (not necessarily derived from std::exception, so
simply specifying that is not an option), and still have the generated
template code for A have the correct throw() specifier for A::Call().

I have a couple of questions:

1. Is having a correct exception specifier even necessary? It's always
been one of those things that C++ has that compilers never seem to
care about (compare to Java, for example, where it is strictly
enforced). VC++ actually generates a compiler warning that throw()
specifiers are ignored, GCC doesn't care, Borland's compiler is the
only one I know of off the top of my head that actually produces
diagnostics when throw() specifications are violated. If it really
doesn't matter (and won't matter in C++0x either), then the above
template stuff is completely unnecessary since I can just declare
Call() with no exception specification at all and not have to deal
with it.

2. If having the correct specification is necessary, is there some
trick I can use to keep you from having to explicitly specify the
exception type template parameter when declaring an A? Some way that I
can make it implicit? In the above program, for example, function() is
declared as throwing a string -- is there some way I can make A<> be
aware of that so I can do "A a(function)" instead of "A<string>
a(function)"? AFAIK function pointer types can't have throw()
information in them so putting it in the A::FN type is not something I
seem to be able to do.

Hopefully this question makes sense; maybe I am thinking about it too
much, but I never really feel comfortable when I throw an exception
from a function that is declared with no exception specifier at all --
plus it's a useful self-documentation tool as well.

Thanks,
Jason
 
J

jason.cipriani

It's always
been one of those things that C++ has that compilers never seem to
care about (compare to Java, for example, where it is strictly
enforced). VC++ actually generates a compiler warning that throw()
specifiers are ignored, GCC doesn't care, Borland's compiler is the
only one I know of off the top of my head that actually produces
diagnostics when throw() specifications are violated.

Also, Comeau doesn't care about things like this either:

void function () throw (A) {
throw NotA();
}
 
K

Kai-Uwe Bux

Consider this program, which defines a template class who's template
parameter is the type of an exception that can be thrown by members of
the class:

=== BEGIN EXAMPLE ===

#include <iostream>
#include <string>
using namespace std;

void function () throw (string) {
throw string("Oops");
}

template <class EX> class A {
public:
typedef void (* FN) ();
explicit A (FN f) : f_(f) { }
void Call () throw (EX) { f_(); }
private:
FN f_;
};

int main () {
A<string> a(&function);
try {
a.Call();
} catch (const string &s) {
cout << s << endl;
}
}

=== END EXAMPLE ===

The goal of the above is to allow the callback function to throw any
exception it wants (not necessarily derived from std::exception, so
simply specifying that is not an option), and still have the generated
template code for A have the correct throw() specifier for A::Call().

What if the callback function can throw objects of various types?

I have a couple of questions:

1. Is having a correct exception specifier even necessary? It's always
been one of those things that C++ has that compilers never seem to
care about (compare to Java, for example, where it is strictly
enforced). VC++ actually generates a compiler warning that throw()
specifiers are ignored, GCC doesn't care, Borland's compiler is the
only one I know of off the top of my head that actually produces
diagnostics when throw() specifications are violated.

You seem to misunderstand throw specifications. They are enforced at
run-time not at compile time. If an exception wants to unwind the stack
past a function from which it is not allowed to escape according to the
exception specification, then the function unexpected() is called.

If it really
doesn't matter (and won't matter in C++0x either), then the above
template stuff is completely unnecessary since I can just declare
Call() with no exception specification at all and not have to deal
with it.

By and large, throw specifications can be considered useless with the
notable exception of throw().


[snip]


Best

Kai-Uwe Bux
 
J

jason.cipriani

You seem to misunderstand throw specifications. They are enforced at
run-time not at compile time. If an exception wants to unwind the stack
past a function from which it is not allowed to escape according to the
exception specification, then the function unexpected() is called.

I did misunderstand; I am so used to compilers enforcing it in Java
that I just kind of assumed C++ would handle them similarly.
Fascinating! I had no idea that you could use set_unexpected() to
change the behavior. In fact, long ago I had once thrown an exception
from a function with a different throw() specification, and the
program terminated. I had immediately assumed that it was because
perhaps the compiler had performed some optimizations or something and
some weird internal error occurred when I threw an exception it didn't
expect to have to handle when compiling the code. I didn't even look
into it any further, that event just made me paranoid and started me
down the road of always using throw() specifications and sticking to
them (which I guess isn't a bad thing but I did it for the wrong
reasons).

So, when I test this with GCC 4.1.2:

class A { };
class B { };
void f1 () throw (A) { throw B(); }
void f2 () { throw B(); }

A call to f1() leads to unexpected() being called, as expected (heh).
However, a call to f2() works just fine. It seems that not specifying
throw(...) at all is the same as saying "this function can throw
anything it wants". That's correct, right? It's not a GCC quirk?

Now, in that case, I still have my original question although for
different reasons. I'd like to be able to enforce strict exception
handling at run-time, and so I'd need to specify the exception type
that the "callback function" can throw in that template class. Right?
Is there a better way to do that? If my goal is to enforce that kind
of exception handling, and my situation is like what it was in my
original post (example at end of this post for reference), is that the
way to do it?
By and large, throw specifications can be considered useless with the
notable exception of throw().

Do you think that they are reliably useful for checking for developer
errors at runtime, at least?

Thanks,
Jason

=== BEGIN EXAMPLE ===
#include <iostream>
#include <string>
using namespace std;
void function () throw (string) {
throw string("Oops");
}
template <class EX> class A {
public:
typedef void (* FN) ();
explicit A (FN f) : f_(f) { }
void Call () throw (EX) { f_(); }
private:
FN f_;
};
int main () {
A<string> a(&function);
try {
a.Call();
} catch (const string &s) {
cout << s << endl;
}
}
 
J

jason.cipriani

Now, in that case, I still have my original question although for
different reasons. I'd like to be able to enforce strict exception
handling at run-time, and so I'd need to specify the exception type
that the "callback function" can throw in that template class. Right?
Is there a better way to do that? If my goal is to enforce that kind
of exception handling, and my situation is like what it was in my
original post (example at end of this post for reference), is that the
way to do it?

Or, do you think, in that situation, it's better to declare Call()
with no throw specification at all, letting it throw anything, and
just let the program abort if whoever is calling Call() doesn't handle
the exception that the callback function throws?

That seems like a simpler solution.

Jason
 
K

Kai-Uwe Bux

Or, do you think, in that situation, it's better to declare Call()
with no throw specification at all, letting it throw anything, and
just let the program abort if whoever is calling Call() doesn't handle
the exception that the callback function throws?

That seems like a simpler solution.

Yes. It is simpler.

An exception specification is like an assert(). However, unlike an assert()
it is usually not locally verifiable especially in templated code or code
that calls functors because in that client supplied stuff might get
executed and throw anything the client felt like throwing.

E.g., look at something simple like

template < typename InIter, typename OutIter, typename T >
OutIter transform ( InIter from, InIter to, OutIter where, T t ) {
while ( from != to ) {
*where++ = t( *from++ );
}
return ( *where );
}

Any line in here might throw. Especially t( *from ) can throw anything and
the assignment operator might try to copy huge data and run out of memory.
A correct throw specification based upon the types is impossible (since
what can be thrown might actually depend on the _value_ of t not just on
the type T).

It is much better to leave the decision on what can be thrown to the client
and in turn let it be the clients responsibility to make sure every
possible exception is handled correctly.


Best

Kai-Uwe Bux
 
J

jason.cipriani

Yes. It is simpler.

That's the way I ended up doing it, rather than messing around with
the throw() specifications.
An exception specification is like an assert(). However, unlike an assert()
it is usually not locally verifiable especially in templated code or code
that calls functors because in that client supplied stuff might get
executed and throw anything the client felt like throwing.

This makes complete sense to me, and clears everything up. Thanks.
It is much better to leave the decision on what can be thrown to the client
and in turn let it be the clients responsibility to make sure every
possible exception is handled correctly.

Thanks a lot for your reply, that answers all my questions.

Jason
 

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

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top