Topic: Proposal: Usable exception specifications
Author: Francis Glassborow <francis.glassborow@ntlworld.com>
Date: Sat, 17 Aug 2002 11:34:22 GMT Raw View
In article <ajjnes$8tr$1@acs2.byu.edu>, Adam H. Peterson
<ahp6@email.byu.edu> writes
>Let us suppose that a function that does not have a throw specification is
>implicitly given a throw specification listing all exceptions that can be
>thrown from the body of this function. We know this can be deduced because
>Java does it, and gives an error when an exception that can be thrown is not
>listed in the specification.
I think this is where your idea hits the stops (I may be wrong, but I
have this policy of stopping reading long posts when I hit the first
major problem).
Java can make these deductions because it never separates declaration
from definition. Java does not really have the concept of separate
compilation.
Now the only way that you can deduce what exceptions can escape from a
function is by seeing its definition AND the exception specifications of
all functions that it calls. However the way we write C++ (and the way
C++ was designed to be written) means that we normally only see the
declarations of functions that are called. That is not enough to achieve
deduction of exception specifications.
Now all the relevant information could be made available at link time,
however we would then hit problems with static functions that are
supposed to have internal linkage and be invisible to the linker.
Putting these things together suggests that any ideas along this route
are going to be extremely complicated to implement and have very
substantial implications and costs (in compile/link resources). Now look
at the experience of Java programmers. Many of these (and highly
competent ones) have very strong reservations about the way exceptions
are enforced in Java.
My conclusion is that empty exception specs are probably useful and
should be continued, the rest should be allowed to die and the industry
encouraged to provide appropriate code analysis tools that will
highlight the potential existence of unhandled exceptions in code. I
think we made a well intentioned error of judgement when we tried to
build in exception checking to the language, run time is too late while
static checking cannot be guaranteed to work.
--
Francis Glassborow ACCU
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Adam H. Peterson" <ahp6@email.byu.edu>
Date: Mon, 19 Aug 2002 12:43:25 CST Raw View
"Francis Glassborow" <francis.glassborow@ntlworld.com> wrote in message
news:lFgsV+B9yhX9Ewh$@robinton.demon.co.uk...
> In article <ajjnes$8tr$1@acs2.byu.edu>, Adam H. Peterson
> <ahp6@email.byu.edu> writes
> >Let us suppose that a function that does not have a throw specification
is
> >implicitly given a throw specification listing all exceptions that can be
> >thrown from the body of this function. We know this can be deduced
because
> >Java does it, and gives an error when an exception that can be thrown is
not
> >listed in the specification.
>
> I think this is where your idea hits the stops (I may be wrong, but I
> have this policy of stopping reading long posts when I hit the first
> major problem).
>
> Java can make these deductions because it never separates declaration
> from definition. Java does not really have the concept of separate
> compilation.
>
> Now the only way that you can deduce what exceptions can escape from a
> function is by seeing its definition AND the exception specifications of
> all functions that it calls. However the way we write C++ (and the way
> C++ was designed to be written) means that we normally only see the
> declarations of functions that are called. That is not enough to achieve
> deduction of exception specifications.
>
> Now all the relevant information could be made available at link time,
> however we would then hit problems with static functions that are
> supposed to have internal linkage and be invisible to the linker.
I agree this is a problem. In order to do this, we would need more than a
.obj and a .h file to compile. The compiler would have to generate an
object file and some sort of dependency file that would list the exception
specifications of the functions defined in the object file.
There are many issues surrounding this solution. First, I think people will
be resistant to such a file in the first place. Second, the file will be
dependant on other such files increasing the complexity of separate
compilation. Either the file will have to be regenerated whenever any of
such files it depends on is regenerated or it will give away a lot of
information about the implementation (such as which functions are called by
which functions) that library writers may not be happy giving out.
I think we have similar issues with exported templates, but at least you can
avoid using exported templates. I also think that these problems can be
solved, although I'm sure there are a lot of unseen complexities. I think
this would encourage library writers to explicitly list the exception specs
for all functions that serve as part of their interface (which might not be
a bad thing).
I don't think these problems are insurmountable, but I do agree that they
might be more complicated than I can conceive of at this time.
>
> Putting these things together suggests that any ideas along this route
> are going to be extremely complicated to implement and have very
> substantial implications and costs (in compile/link resources). Now look
> at the experience of Java programmers. Many of these (and highly
> competent ones) have very strong reservations about the way exceptions
> are enforced in Java.
My biggest reservations about Java's exception specs are 1) they are
mandatory, often interfering with clear program flow when they could have
been deduced. 2) Exceptions can still be thrown that are not listed in the
spec, rendering them almost useless in my view. I'm not sure what other
people's concerns about them are, although I believe I've heard the above
two frequently.
>
> My conclusion is that empty exception specs are probably useful and
> should be continued, the rest should be allowed to die and the industry
> encouraged to provide appropriate code analysis tools that will
> highlight the potential existence of unhandled exceptions in code. I
> think we made a well intentioned error of judgement when we tried to
> build in exception checking to the language, run time is too late while
> static checking cannot be guaranteed to work.
I agree that empty specs are valuable. My issue with them is, how do you
take a function f() that calls a function g() that may throw anything and
make f() throw nothing? You have to know exactly what exceptions g()
throws, and I don't see how we can know that. If we could find a solution
that made empty throw specs work, I would be thrilled. (Essentially I want
to declare main() to throw nothing and make sure all exceptions are caught.)
But I don't see how that can be done usably unless you keep track of them
all.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: rmaddox@isicns.com (Randy Maddox)
Date: Mon, 19 Aug 2002 12:45:17 CST Raw View
Francis Glassborow <francis.glassborow@ntlworld.com> wrote in message news:<lFgsV+B9yhX9Ewh$@robinton.demon.co.uk>...
> In article <ajjnes$8tr$1@acs2.byu.edu>, Adam H. Peterson
> <ahp6@email.byu.edu> writes
> >Let us suppose that a function that does not have a throw specification is
> >implicitly given a throw specification listing all exceptions that can be
> >thrown from the body of this function. We know this can be deduced because
> >Java does it, and gives an error when an exception that can be thrown is not
> >listed in the specification.
>
> I think this is where your idea hits the stops (I may be wrong, but I
> have this policy of stopping reading long posts when I hit the first
> major problem).
>
> Java can make these deductions because it never separates declaration
> from definition. Java does not really have the concept of separate
> compilation.
>
> Now the only way that you can deduce what exceptions can escape from a
> function is by seeing its definition AND the exception specifications of
> all functions that it calls. However the way we write C++ (and the way
> C++ was designed to be written) means that we normally only see the
> declarations of functions that are called. That is not enough to achieve
> deduction of exception specifications.
>
> Now all the relevant information could be made available at link time,
> however we would then hit problems with static functions that are
> supposed to have internal linkage and be invisible to the linker.
>
> Putting these things together suggests that any ideas along this route
> are going to be extremely complicated to implement and have very
> substantial implications and costs (in compile/link resources). Now look
> at the experience of Java programmers. Many of these (and highly
> competent ones) have very strong reservations about the way exceptions
> are enforced in Java.
>
> My conclusion is that empty exception specs are probably useful and
> should be continued, the rest should be allowed to die and the industry
> encouraged to provide appropriate code analysis tools that will
> highlight the potential existence of unhandled exceptions in code. I
> think we made a well intentioned error of judgement when we tried to
> build in exception checking to the language, run time is too late while
> static checking cannot be guaranteed to work.
>
>
> --
> Francis Glassborow ACCU
> 64 Southfield Rd
> Oxford OX4 1PA +44(0)1865 246490
> All opinions are mine and do not represent those of any organisation
I agree with Francis 100%, and this hasn't yet even gotten into the
issue of dynamically linked shared libraries (.dll and .so), for which
we see only header files and don't see the executable code until run
time. Since most larger applications rely heavily on composition from
shared libraries, for which we might not even have source code to see
any exception specifications, I don't see how this could ever work for
C++.
Randy.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: alan@octopull.demon.co.uk (Alan Griffiths)
Date: Mon, 19 Aug 2002 12:52:18 CST Raw View
"Adam H. Peterson" <ahp6@email.byu.edu> wrote in message news:<ajjnes$8tr$1@acs2.byu.edu>...
> Here, if you will bear with me, is my idea for obtaining usable exception
> specifications in C++.
This interests me as I am unhappy with the current situation. (And
even more unhappy with the situation in Java.)
> Note that it will break code,
That is, of course far from ideal. In practice, it means that
supporters of the proposal will need to identify the types of code
that breaks and estimate the impact in terms of the number and type of
changes it forces on users. C++ vendors will particularly concerned
with the ease of supporting backward compatability modes.
> and I may not have
> thought all the implementation issues through thoroughly, but I think the
> idea has merit,
I think you should identify the benefits you believe accepting the
proposal would bring.
> and I'd like to know what its major weaknesses are.
>
> Let us suppose that a function that does not have a throw specification is
> implicitly given a throw specification listing all exceptions that can be
> thrown from the body of this function. We know this can be deduced because
> Java does it, and gives an error when an exception that can be thrown is not
> listed in the specification.
I disagree with both your premise and your conclusion:
o Java can not do this. What it does do is check the implementation
of a method against its signature. To do what you suggest in Java
would require it to deduce the signature of abstract methods.
Also, it is possible to change the exception signature but fail to
recompile client code. Java makes no attempt to detect this and
the program can be run.
o In C++ this could not possibly be checked until link time as
the source of referenced functions is not available in the
client compilation unit.
In view of the commonality of dynamic linking implementations
this means that a LOT of exception specification information needs
to be stored and processed.
> Next any function that gives a throw specification now has that
> specification checked at compile time. A function that has a throw
> specification but that can throw an exception that isn't listed in the
> specification makes the program ill-formed with a required diagnostic.
The experience of Java convinces me that this is a bad idea.
Firstly, Java doesn't attempt to validate all exceptions: Errors and
RuntimeExceptions are not checked. The designers of Java correctly
concluded that this would be too much of a burden on developers.
Secondly, Java's checked exceptions are essentially a short range
mechanism to pass failure messages back to the immediate client code.
C++ exceptions are essentially a long range mechanism for passing
failure messages back to the subsystem interface.
--
Alan Griffiths alan@octopull.demon.co.uk ACCU Chairman chair@accu.org
http://www.octopull.demon.co.uk/ http://accu.org/
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: news_comp.std.c++_expires-2002-10-01@nmhq.net (Niklas Matthies)
Date: Mon, 19 Aug 2002 22:30:02 GMT Raw View
On 19 Aug 2002 17:45:04 GMT, Adam H. Peterson <ahp6@email.byu.edu> wrote:
[...]
> I think we have similar issues with exported templates, but at least
> you can avoid using exported templates. I also think that these
> problems can be solved, although I'm sure there are a lot of unseen
> complexities. I think this would encourage library writers to
> explicitly list the exception specs for all functions that serve as
> part of their interface (which might not be a bad thing).
I rather suspect it would "encourage" library writers to not be specific
about which particular exceptions their functions may throw, and to
instead just specify some common base type (say, std::exception).
-- Niklas Matthies
--
Nothing is fool-proof to a sufficiently talented fool.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Daniel James <internet@nospam.demon.co.uk>
Date: Tue, 20 Aug 2002 18:46:29 GMT Raw View
In article
<slrnam2o3k.r8.news_comp.std.c++_expires-2002-10-01@nightrunner.nmhq.net>,
Niklas Matthies wrote:
> I rather suspect it would "encourage" library writers to
> not be specific about which particular exceptions their
> functions may throw, and to instead just specify some
> common base type (say, std::exception).
Even that would be useful information in many cases.
The point of exception specs should be to enable a compiler to produce a
diagnostic when the ES part of a function's contract is inconsistent with its
implementation so that the programmer can be sure that every exception that can
be thrown is caught (somewhere).
If a library header indicates that the functions in the library can only throw
std::exception (or its offspring) we do at least know that they won't throw
proprietary::exception (or char*, or anything else the library's user may not
have thought to handle at a higher level).
Cheers,
Daniel.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: Daniel James <internet@nospam.demon.co.uk>
Date: Tue, 20 Aug 2002 18:46:48 GMT Raw View
In article <ajjnes$8tr$1@acs2.byu.edu>, Adam H. Peterson
wrote:
> Let us suppose that a function that does not have a throw
> specification is implicitly given a throw specification
> listing all exceptions that can be thrown from the body
> of this function. We know this can be deduced because
> Java does it, and gives an error when an exception that
> can be thrown is not listed in the specification.
I'd suggest that you *don't* look at what Java does when
deciding what you think C++ should do.
I don't like your idea of "deduced" ESs. The "C-like" way is
to require the programmer to specify the exceptions that a
function may throw and to require the compiler to generate a
diagnostic if the implementation of the function can actually
emit more exceptions than the ES permits.
Here are some of my thoughts on the matter (not worked out as
carefully as they might be, but bear with me)...
As C++ is currently specified you can't always (usually) tell
when exceptions a piece of code can emit because the ES is
not a part of the type of a function, so a function defined
as:
int foo( int j ) throws( std::domain_error )
{
if( j>INT_MAX/2 )
{
throw std::domain_error( "outsize arg in foo" );
}
return j*2;
}
would compile OK because it can only throw std::domain error
and that exception is mentioned in its ES.
If we replace that example with:
extern int bar( int i );
int foo2( int j ) throw( std::domain_error )
{
if( j>INT_MAX/2 )
{
throw std::domain_error( "outsize arg in foo" );
}
return bar(j*2);
}
the compiler currently has no way to know whether bar() can
throw anything at all, so cannot check the ES for foo2.
If we change the language so that bar's declaration must also
include its ES:
extern in bar( int j ) throw( std::invalid_argument );
the compiler would be able to check bar's ES at compile time,
and discover that there's a problem.
[And now I can replace INT_MAX with
std::numeric_limits<int>::max(), which the compiler will know
is nothrow because it'll be defined that way in <limits>. ]
Note, though, that there are a number of things that make
this putative language change quite problematic. If one
function takes another function as an argument (and calls it)
it may emit any exception thrown by the argument:
int baz( int j, int (*qux)(int) )
throw( std::domain_error )
{
if( j>std::numeric_limits<int>::max()/2 )
{
throw std::domain_error( "outsize arg in baz" );
}
return qux(j*2);
}
This is OK, as shown, *only* if the actual parameter passed
as qux is nothrow or throws only domain_error. If we wanted
qux to be able to throw a different exception type we would
have to know what that type is when baz is code. i.e. we'd
have to specify the ES of qux as part of the type of the
format parameter (which is not currently possible in C++).
Say we wanted qux to be able to throw some type UserException
then we'd have something like:
int baz( int j, int (*qux)(int) throw( UserException ) )
throw( std::domain_error, UserException )
{
...
I think that's an acceptable solution *if* we accept that baz
may place such a constraint on the ES of the function passed
as its argument qux. I can already hear people recoiling in
horror at the complexity of the declaration, though <smile>.
Clearly this sort of syntax would also be applicable to
function pointers:
int (*pfunc)(int) throw(UserException) = qux;
and typedefs of such pointer types should also be allowed:
typedef int (*QuxFunc)(int) throw(UserException);
QuxFunc pqux = qux;
QuxFunc pqux2 = baz; // Error: ESs incompatible
Templates exacerbate the problem we've just seen with baz. We
can imagine a syntax that allows ESs to be template arguments
in the same way as other occurrences of types. Imagine a
templated function print that sends to cout a string
representation of an object derived from a (any) class that
has a GetStringRep() method. If we knew that GetStringRep()
may throw an exception of a single type we could write:
template <typename T, typename E>
void print( T arg ) throw( E, ios_base::failure )
{
std::cout << arg.GetStringRep() << sd::endl;
}
Given classes MyWidget and YourWidget we might have:
std::string MyWidget::GetStringRep() throw( MyUDE );
std::string YourWidget::GetStringRep() throw( YourUDE );
and the template above would be able to instantiate the
functions
void print( MyWidget arg )
throw( MyUDE, ios_base::failure );
and
void print( YourWidget arg )
throw( YourUDE, ios_base::failure );
but would have trouble with class TrickyWidget that has:
std::string TrickyWidget::GetStringRep()
throw( char *, std::exception );
We could make exception checking for our print template work
for selected cases, but to do so we would have to use a
defintion of print that constrains the classes T to those
whose GetStringRep() method have an ES compatible with print.
This sort of constraint becomes particularly important when
we consider templated container classes, such as those in the
standard library. For example: the standard containers make
copies of the items that are stored in the container. To do
this they use the copy constructor and/or assignment operator
of the class of the object being stored. If we were to
require the containers to specify an ES for methods like
push_back then the containers could only hold elements of
types whose copy/assignment members had ESs that were at
least as restricting. (Though it's arguable that
copy/assignment methods should be constrained to be nothrow
anyway.)
Of course, we cn make the template problem "go away" by just
not specifying an ES for templated functions:
template <typename T>
void print( T arg )
{
std::cout << arg.GetStringRep() << sd::endl;
}
but here we're saying the print can throw anything (as,
indeed, it can) and that will cause problems for ES checking
in print's caller....
void foo( MyWidget w ) throw( MyUDE, ios_base::failure )
{
print( w ); // Error: ES mismatch
}
Currently, this should not be a problem because print is a
template expanded inline, and the compiler can tell that the
only exceptions it can throw are those from
MyWidget::GetStringRep() and from the i/o operations. That
will not be true of implementations that implement export.
> There are issues here, of course. First, polymorphic
> functions. The Liskov substitution principle suggests
> that any overriding function should not be able to throw
> anything that a parent version is not allowed to throw.
Agreed.
> Second issue, templates. It is often the case that a
> template does not necessarily know what exceptions are
> thrown by the operations on its type parameters.
Indeed, as I noted above.
> For example, std::swap may give as its exception
> specification something like this:
>
> namespace std {
> template<typename T>
> void swap(T &t1, T &t2) throw(T::operator=(T &), T::T(T
&));
> }
In that /particular/ case I think I'd prefer:
template <typename T>
void swap( T &t1, T &t2 ) throw();
I think that, in general, what you are loooking for is an
exception_specification_of construct, like sizeof and the
oft-demanded typeof. I'm not sure that you need that as long
as you can specify the ES of a function template in the same
way that you can specify other typenames involved in its
defintion.
> In particular, if swap were given above with no exception
> specification, its specification would be deduced from its
> body and we'd have in effect the same thing.
No. I don't like deduction. The declaration of a function
should list it's ES in full as part of the documentation of
the contract between the function and its callers, the
compiler should just tell you when you've got it wrong.
> It may also be the situation where you want the
> specification to be deduced from the body, but you also
> want to allow certain exceptions beyond this. I
> propose that we allow the use of an ellipsis (...)
> list to suggest that the specification will be deduced, ...
I was going to suggest that throw(...) should mean the same
as not specifiying an ES at all - anything may be thrown.
It's just a little clearer to have an explicit way to say it
(and compilers could then be made to warn when a function
without an ES was encountered).
If you *must* have a syntax for a 'deduced' ES I'd suggest
throw(auto) - but I don't like deduction.
> A third issue is that according to my knowledge the
> standard allows many library functions to throw exceptions
> not enumerated by the standard. I'm not sure I have a
> solution to that, except to have the standard be tighter
> about these allowances, and/or possibly requiring they
> conform to a specific hierarchy (although that may be
> impractical).
Hmm. there's a lot to be said for encouraging programmers to
derive their exceptions from a small number of base classes.
If std::exception were a more useful class (by which I mean
mostly that it should be more amenable to
inernationalization) I wouldn't mind everything being derived
it.
> (Note: A design goal of this proposal is to require
> exception specifications in as few places as possible
> for those who wish not to use them, and I'd
> welcome any suggestions on this front.)
As I read it, that's not compatible with your stated aims. If
you want compile-time exception checking you have to provide
an ES with every function declaration and ensure that that ES
is visible wherever the function is called. If you don't
provide ESs what is there for the compiler to check?
> Another aspect of this proposal is that we need a syntax
> to allow a function to throw anything it wishes without
> enumerating all types.
We have that now - just don't provide an ES. Yes, I'd like to
see throw(...) as an explicit way of writing that. The
problem is that the caller of any such function must either
also have a throw(...) ES, or must explicitly catch and
handle every type of exception that is not in it's ES...
void wanton() throw(...);
void meek() throw( std::exception )
{
...
try
{
wanton();
}
catch( std::exception )
{
throw;
}
catch(...)
{
// swallow any exception from wanton
// that meek can't throw - should maybe
// assert here, or throw something that
// *is* allowed by meek()
}
}
Hmmm... or maybe somthing like:
void meek() throw( std::exception )
{
...
reinterpret_cast<void ()() throw(std::exception)>
(wanton)();
}
> I am inclined to believe that with these rules in place,
> it would be easier for people who desire to make sure
> that all exceptions are caught by putting an empty
> specification (throw()) for main() and correct all
> resulting diagnostics.
I think your heart's in the right place, but I don't like
your idea of getting the compiler to deduce ESs.
Firstly: because it's really hard to do because it requires
analysis of all the code in the whole program (and in any
libraries linked with it - a showstopper if you don't have
the source).
Secondly: because I see ESs written by the programmer as
being a potentially very useful way of documenting and
enforcing contracts.
Cheers,
Daniel.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: apm35@student.open.ac.uk (apm)
Date: Wed, 21 Aug 2002 15:43:39 GMT Raw View
"Adam H. Peterson" <ahp6@email.byu.edu> wrote in message news:<ajjnes$8tr$1@acs2.byu.edu>...
> Here, if you will bear with me, is my idea for obtaining usable exception
> specifications in C++.
[snip]
and here's mine:
http://www.andrewmarlow.co.uk/publications.html
I withdrew the proposal for a number of reasons but the document is
still useful IMO beause it discusses the issues that come up again and
again with ESs.
Regards,
Andrew M.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Adam H. Peterson" <ahp6@email.byu.edu>
Date: Fri, 16 Aug 2002 23:11:40 GMT Raw View
Here, if you will bear with me, is my idea for obtaining usable exception
specifications in C++. Note that it will break code, and I may not have
thought all the implementation issues through thoroughly, but I think the
idea has merit, and I'd like to know what its major weaknesses are.
Let us suppose that a function that does not have a throw specification is
implicitly given a throw specification listing all exceptions that can be
thrown from the body of this function. We know this can be deduced because
Java does it, and gives an error when an exception that can be thrown is not
listed in the specification.
Next any function that gives a throw specification now has that
specification checked at compile time. A function that has a throw
specification but that can throw an exception that isn't listed in the
specification makes the program ill-formed with a required diagnostic.
There are issues here, of course. First, polymorphic functions. The Liskov
substitution principle suggests that any overriding function should not be
able to throw anything that a parent version is not allowed to throw. This
would imply that whenever a virtual function is declared in the most-base
class, it should be given an exception specification that applies to all
children. Children may make this more strict by removing throwable
exception types, but not more loose.
This is probably a good idea anyway from the point of view of design
(although I hesitate to impose it on people who would rather not have to
deal with it).
Second issue, templates. It is often the case that a template does not
necessarily know what exceptions are thrown by the operations on its type
parameters. I propose that we extend the exception specification syntax so
that in addition to listing type names, function signatures and
constructor(/destructor?) denotations may also be listed. These function
signatures expand into the exception specification for the denoted
functions.
For example, std::swap may give as its exception specification something
like this:
namespace std {
template<typename T>
void swap(T &t1, T &t2) throw(T::operator=(T &), T::T(T &));
}
Note that in many cases this may not be necessary. In particular, if swap
were given above with no exception specification, its specification would be
deduced from its body and we'd have in effect the same thing.
Also note that the functions given above were non-const versions. Because a
template would not necessarily know which version of the function exists and
is used, it would want to list the most permissive usable version of the
function. If this function doesn't exist, the compiler would substitute
whichever function would be used to fulfill the indicated operation. In the
above case, these would most likely be T::operator=(T const&) and T::T(T
const&).
It may also be the situation where you want the specification to be deduced
from the body, but you also want to allow certain exceptions beyond this. I
propose that we allow the use of an ellipsis (...) at the beginning of the
list to suggest that the specification will be deduced, but then loosened.
This may be useful for a polymorphic function with a nontrivial body that
will be overridden by derived versions that will throw other exceptions, or
perhaps a polymorphic member function of a template class.
Thus,
class X {
public:
virtual void vf() throw(...,std::exception);
};
A third issue is that according to my knowledge the standard allows many
library functions to throw exceptions not enumerated by the standard. I'm
not sure I have a solution to that, except to have the standard be tighter
about these allowances, and/or possibly requiring they conform to a specific
hierarchy (although that may be impractical).
(Note: A design goal of this proposal is to require exception specifications
in as few places as possible for those who wish not to use them, and I'd
welcome any suggestions on this front.)
Another aspect of this proposal is that we need a syntax to allow a function
to throw anything it wishes without enumerating all types. A possible
syntax for this might be throw(...) (with no types or functions listed after
the ellipsis). Thus someone who wishes to disable throw specifications
completely simply puts throw(...) after every virtual function and leaves
off all other specifications.
It would probably break less code if the defaults given above were switched
such that if no specification is given, anything may be thrown, and
throw(...) would mean only throw what is deduced from the body of the
function. This would be closer to how they work in current code as well (as
broken as that is from a design standpoint). My only hesitation is that I
wish the default to be that the specification is deduced so that people who
are trying to shore up their exception specifications may do so with
third-party code that doesn't use them. Perhaps this default should only be
switched for polymorphic functions, since these functions are probably the
only functions that would really need to loosen their specification beyond
what their body already throws.
I am inclined to believe that with these rules in place, it would be easier
for people who desire to make sure that all exceptions are caught by putting
an empty specification (throw()) for main() and correct all resulting
diagnostics. (This wouldn't cover exceptions occurring during the
construction and destruction of namespace level objects, but I don't know
what to do about that.)
Anyway, thanks for reading it, and I'd like to know what you think.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]