diff --git a/doc/boost-exception.html b/doc/boost-exception.html index dd16813..fc85cbb 100644 --- a/doc/boost-exception.html +++ b/doc/boost-exception.html @@ -25,10 +25,13 @@
The ability to add data to exception objects after they have been passed to throw is important, because often some of the information needed to handle an exception is unavailable in the context where the failure is detected.
Boost Exception also supports N2179-style copying of exception objects, implemented non-intrusively and automatically by the boost::throw_exception function.
Deriving from boost::exception effectively decouples the semantics of a failure from the information that is relevant to each individual instance of reporting a failure with a given semantic.
+In other words: with boost::exception, what data a given exception object transports depends primarily on the context in which failures are reported (not on its type). Since exception types need no members, it becomes very natural to throw exceptions that derive from more than one type to indicate multiple appropriate semantics:
+struct exception_base: virtual std::exception, virtual boost::exception { };
+struct io_error: virtual exception_base { };
+struct file_error: virtual io_error { };
+struct read_error: virtual io_error { };
+struct file_read_error: virtual file_error, virtual read_error { };
+Using this approach, exception types become a simple tagging system for categorizing errors and selecting failures in exception handlers.
+Traditionally, when using exceptions to report failures, the throw site:
+A higher context in the program contains a catch statement which:
+The main issue with this "traditional" approach is that often, the data available at the point of the throw is insufficient for the catch site to handle the failure.
+Here is an example of a catch statement:
+catch( file_read_error & e ) + { + std::cerr << e.file_name(); + }+
And here is a possible matching throw:
+void +read_file( FILE * f ) + { + .... + size_t nr=fread(buf,1,count,f); + if( ferror(f) ) + throw file_read_error(???); + .... + }+
Clearly, the problem is that the handler requires a file name but the read_file function does not have a file name to put in the exception object; all it has is a FILE pointer!
+In an attempt to deal with this problem, we could modify read_file to accept a file name:
+void +read_file( FILE * f, char const * name ) + { + .... + size_t nr=fread(buf,1,count,f); + if( ferror(f) ) + throw file_read_error(name); + .... + }+
This is not a real solution: it simply shifts the burden of supplying a file name to the immediate caller of the read_file function.
++In general, the data required to handle a given library-emitted exception depends on the program that links to it. Many contexts between the throw and the catch may have relevant information which must be transported to the exception handler.
The idea of exception wrapping is to catch an exception from a lower level function (such as the read_file function above), and throw a new exception object that contains the original exception (and also carries a file name.) This method seems to be particularly popular with C++ programmers with Java background.
+Exception wrapping leads to the following problems:
+The second point is actually special case of violating the exception neutrality principle. Most contexts in a program can not handle exceptions; such contexts should not interfere with the process of exception handling.
+For example, in the throw statement below we only add the errno code, since this is the only failure-relevant information available in this context:
+struct exception_base: virtual std::exception, virtual boost::exception { }; +struct file_read_error: virtual exception_base { }; + +typedef boost::error_info<struct tag_errno_code,int> errno_code; + +void +read_file( FILE * f ) + { + .... + size_t nr=fread(buf,1,count,f); + if( ferror(f) ) + throw file_read_error() << errno_code(errno); + .... + }+
In a higher exception-neutral context, we add the file name to any exception that derives from boost::exception:
+typedef boost::error_info<struct tag_file_name,std::string> file_name; + +.... +try + { + if( FILE * fp=fopen(“foo.txt”,”rt”) ) + { + shared_ptr<FILE> f(fp,fclose); + .... + read_file(fp); //throws types deriving from boost::exception + do_something(); + .... + } + else + throw file_open_error() << errno_code(errno); + } +catch( boost::exception & e ) + { + e << file_name(“foo.txt”); + throw; + }+
Finally here is how the handler retreives data from exceptions that derive from boost::exception:
+catch( io_error & e ) + { + std::cerr << “I/O Error!\n”; + + if( shared_ptr<std::string const> fn=get_error_info<file_name>(e) ) + std::cerr << “File name: “ << *fn << “\n”; + + if( shared_ptr<int const> c=get_error_info<errno_code>(e) ) + std::cerr << “OS says: “ << strerror(*c) << “\n”; + }+
boost/exception/diagnostic_information.hpp
@@ -45,6 +44,8 @@Exception Types As Simple Semantic Tags
Integrating Boost Exception in Existing Exception Class Hierarchies
+Using Virtual Inheritance in Exception Types
All exception types that derive from boost::exception can be used as type-safe containers of arbitrary data objects, while complying with the no-throw requirements (15.5.1) of the ANSI C++ standard for exception types. Data can be added to a boost::exception at the time of the throw, or at a later time.
+All exception types that derive from boost::exception can be used as type-safe containers of arbitrary data objects, while complying with the no-throw requirements (15.5.1) of the ANSI C++ standard for exception types.
+When exceptions derive from boost::exception, arbitrary data can be added to exception objects:
+The following example demonstrates how errno can be stored in exception objects using Boost Exception:
diff --git a/doc/using_virtual_inheritance_in_exception_types.html b/doc/using_virtual_inheritance_in_exception_types.html new file mode 100644 index 0000000..a846220 --- /dev/null +++ b/doc/using_virtual_inheritance_in_exception_types.html @@ -0,0 +1,61 @@ + + + + +Exception types should use virtual inheritance when deriving from other exception types. This insight is due to Andrew Koenig. Using virtual inheritance prevents ambiguity problems in the exception handler:
+#include <iostream> +struct my_exc1 : std::exception { char const* what() const throw(); }; +struct my_exc2 : std::exception { char const* what() const throw(); }; +struct your_exc3 : my_exc1, my_exc2 {}; + +int +main() + { + try { throw your_exc3(); } + catch(std::exception const& e) {} + catch(...) { std::cout << "whoops!" << std::endl; } + }+
The program above outputs "whoops!" because the conversion to std::exception is ambiguous.
+The overhead introduced by virtual inheritance is always negligible in the context of exception handling. Note that virtual bases are initialized directly by the constructor of the most-derived-type (the type passed to the throw statement, in case of exceptions.) However, typically this detail is of no concern when boost::exception is used, because it enables exception types to be trivial structs with no members (there's nothing to initialize.) See Exception Types As Simple Semantic Tags.
+