std::exception
C++ supports throwing objects of user-defined types, which allows arbitrary data to be stored in exception objects
at the point of the throw
. Unfortunately at that time we may not have available all of the data that is needed
at the catch
site to properly deal with the error. However, that data is usually available in contexts
higher in the call stack, between the throw
and the catch
that handles the exception.
This document proposes extending std::exception
to allow exception-neutral contexts to intercept
any std::exception
object and store into it arbitrary additional data needed to handle the exception (the propagation of
the original exception object can then be resumed by a throw
without argument.)
To illustrate the problem, consider the following example of a catch
statement that handles an exception of a user-defined type
file_read_error
:
catch( file_read_error & e ) { std::cerr << e.file_name(); }
And the matching throw
:
void read_file( FILE * f ) { .... size_t nr=fread(buf,1,count,f); if( ferror(f) ) throw file_read_error(???); .... }
The issue is that the catch
site needs a file name, but at the point of the throw
a file name is not available (only a FILE
pointer is.)
Consider that the error might be detected in a library which can not assume that a meaningful name is
available for any FILE
it reads, even if a program that uses the library could reasonably make the same
assumption.
The proposed extension to std::exception
allows a program to augment library exceptions
with program-specific information required to properly handle them:
struct errinfo_file_name { typedef std::string type; }; void process_file( char const * name ) { try { if( FILE * fp=fopen(name,"rt") ) { std::shared_ptr<FILE> f(fp,fclose); .... read_file(fp); //throws types deriving from std::exception .... } else throw file_open_error(); } catch( std::exception & e ) { e.set<errinfo_file_name>(name); throw; } }
The handler can retrieve the file name using the following syntax:
catch( file_io_error & e ) //derives (possibly indirectly) from std::exception { std::cerr << "I/O error!\n"; std::string const * fn=e.get<errinfo_file_name>(e); assert(fn!=0); //In this program all files have names. std::cerr << "File name: " << *fn << "\n"; }
Extending std::exception
as proposed is compatible with existing code and does not affect any other parts
of the standard library, except possibly make_exception_ptr
and current_exception
.
Add to std::exception
the following member functions:
template <class Tag> void set( typename Tag::type const & x );
Requirements: x
shall be copyable.
Effects: A copy of x
is stored in this
. If this
already
has a value stored under the specified Tag
, the original value is overwritten.
Postconditions: get<Tag>()
returns a pointer to the stored copy of x
.
Throws: std::bad_alloc
or any exception thrown by T
's copy or move constructor.
Note: It is permitted for copies of a std::exception
object to share storage
for the objects stored by set<>
, except for copies created by std::make_exception_ptr
or
std::current_exception
. Therefore calling set<>
on copies of a std::exception
is not thread-safe unless the copies were created by std::make_exception_ptr
or std::current_exception
.
template <class Tag> typename Tag::type const * get() const;
Returns: If set<Tag>
was called to store a copy of an object of type typename Tag::type
in this
, get<Tag>
returns a pointer to that copy; otherwise it returns 0.
Throws: does not throw.
Note: Destroying the exception object or calling set<>
invalidates the returned pointer.
std::string diagnostic_information() const;
Returns: A string of unspecified format that contains human-readable diagnostic information about this
.
Throws: May throw std::bad_alloc
or any exception thrown in the attempt to convert to string any of the objects
stored in this
by set<>
.
Notes:
Implementations are encouraged to include in the returned string information about the dynamic type of this
,
for example by means of typeid(*this).name()
.
Implementations are encouraged to include in the returned string the string returned by what
.
Implementations are encouraged to include in the returned string information collected by __FILE__
and __LINE__
at the point of the throw
. The formatting may match the format of compile-time diagnostic messages.
Implementations are encouraged to include in the returned string the name of the function contaning the throw
.
At the time diagnostic_information
is called, implementations may convert to string any of the objects
stored in this
by set<>
, by calling a suitable operator<<
overload that takes
std::ostream
object on the left and typename Tag::type
object on the right, bound at the point of
instantiation of set<Tag>
. Implementations are not allowed to issue a diagnostic if a suitable overload could
not be bound.
At the time diagnostic_information
is called, objects for which a suitable operator<<
overload
could not be bound may be converted to string if a different reasonable string conversion is possible. For example,
if an operator<<
overload suitable for converting objects of type T
can be bound, a
std::vector<T>
may be converted to string by calling that overload for each element of the vector
and concatenating the results.
At the time diagnostic_information
is called, objects of type exception_ptr
stored in this
by set<>
may be converted to string by nesting the result of calling diagnostic_information
on the object they point to.
Implementations are encouraged to pair each converted to string object with a string representation of its tag type, for example
by means of typeid(Tag).name()
, and to include both in the returned string. If a given object could not be reasonably
converted to string, an implementation-specified stub string may be used instead.
Implementations may include in the returned string any other relevant information, such as a stack trace
collected at the point of the throw
.
Example:
example_io.cpp(70): Throw in function class std::shared_ptr<FILE> open_file(const char *,const char *) Dynamic exception type: struct file_open_error what: example_io error errinfo_api_function = fopen errinfo_errno = 2, "No such file or directory" errinfo_file_name = tmp1.txt
Add the following note to the specification of current_exception
(18.8.5 [propagation]):
Note: If the current exception derives from std::exception
, the exception object that is propagated
when the returned exception_ptr
is passed to rethrow_exception
shall include copies of
all objects stored in the original exception object by std::exception::set<>
.
Add the following note to the specification of make_exception_ptr
(18.8.5 [propagation]):
Note: If E
derives from std::exception
, the exception object that is propagated
when the returned exception_ptr
is passed to rethrow_exception
shall include copies of
all objects stored in the original exception object by std::exception::set<>
.
A proof of concept implementation is available in Boost. See boost/exception/N3757.hpp and libs/exception/test/N3757_test.cpp in http://svn.boost.org/svn/boost/trunk.
This proposal is based on the Boost Exception library and incorporates valuable feedback from the Boost community. Special thanks to Peter Dimov and Dave Abrahams.