Doc. no. N2229==07-0089
Date:
2007-05-05
Project: Programming Language C++
Reply to: Beman Dawes <bdawes@acm.org>
Preface
Introduction
Proposed changes to the working paper
Rationale for mixin approach
Impact on the Standard
Impact on existing code
Implementation experience using the proposed mixin
Acknowledgements
Revision history
Demonstration program
An alternate proposal, N2179, Language Support for Transporting Exceptions between Threads, by Peter Dimov, was accepted in principle by the C++ Committee's Enhancements Working Group at the Oxford meeting. That proposal is considered superior to the pure-library approach presented here, so this proposal is of interest only if the N2179 proposal stalls.
The underlying problem addressed in this proposal is a need to catch an exception by dynamic type (i.e. most-derived class), save it and later rethrow it, knowing only the static type (i.e. base class), and then finally to catch it again using the derived type.
Although this behavior might be useful elsewhere, it becomes a pressing need if exceptions are to be propagated from a thread-of-execution to a different thread.
Here is a non-threaded example of the problem, expressed in psuedo-C++:
class B { /* ... */ virtual ~B(); }; class D : public B { /*...*/ }; void f() { throw D(); } int g() { B * p = 0; try { f(); } catch ( const B & ex ) { p = dynamic_new B(ex); // effectively: new D(ex) } // ... try { if ( p ) dynamic_throw p; // type thrown is D } catch ( const D & ex ) { // we want this catch to be executed } catch ( const B & ex ) { // we don't want this catch to be executed } }
The above code will not currently work, because the language does not supply
the dynamic_new
and dynamic_throw
or equivalent
operators, and that is the heart of the problem addressed by this proposal.
This proposal does not require language or compiler support, and has been implemented in C++03. It allows exception propagation from threads under the current language rules, but is also useful outside a threading context.
An early version of this paper was discussed at the Redmond ad-hoc threads meeting in August, 2006. There was a strong consensus to go forward with a pure library-based approach to exception propagation from threads, while also pursuing solutions requiring core language or compiler support. Thus the pure-library approach would be available as a fallback if the core language or compiler support approaches ended up being unacceptable to the committee.
In a header to be decided, add:
namespace std { class cloneable { public: typedef shared_ptr<cloneable> ptr_type; virtual ptr_type dynamic_clone() const=0; virtual void dynamic_throw() const=0; virtual ~cloneable(){} }; }Class
cloneable
Class
cloneable
provides a mixin base class for objects that need to clone or throw themselves based on dynamic type.[Examples:
class my_exception : public std::exception { public: ptr_type dynamic_clone() const { return ptr_type(new my_exception(*this)); } void dynamic_throw() const { throw *this; } // ... };
class non_std_exception : public virtual std::cloneable { public: ptr_type dynamic_clone() const { return ptr_type(new non_std_exception(*this)); } void dynamic_throw() const { throw *this; } // ... };-- end examples]
To 18.7.1, Class exception, change:
class exception {
to:
class exception : public virtual cloneable {
and add:
ptr_type dynamic_clone() const;Returns:
ptr_type(new exception(*this))
void dynamic_throw() const;Effects:
throw *this
Users will need to write catch clauses for specific exception classes, both
Standard Library and user-defined. To implement this behavior, it must be
possible save and re-throw exceptions without knowing anything more than their
base class. The dynamic_clone
and dynamic_throw
functions
make this possible. Using a mixin base class allows use in user-defined exception types,
whether or not they are derived from std::exception
.
This is a pure library proposal. It can be portably
implemented with any C++03 compiler. It's impact on the standard is limited to
the change to std::exception
.
No practical impact foreseen. It is remotely possible that some existing code somehow depends on std::exception having no base class, but that seems pretty unlikely.
The proposal has been implemented and tested, both in an multi-threading environment using Boost.Threads, and in a non-multithreading environment. No problems were encountered in either environment, and exceptions of the desired type were successfully propagated.
Alisdair Meredith suggested the mixin approach rather than adding the
functionality directly to std::exception
, and
also provided an
alternative approach that did not require changes to exception classes (but did
require changes to code that throws exceptions.) Peter Dimov and Howard Hinnant
provided helpful comments to the original paper.
N2229, Cloning and Throwing Dynamically Typed Exceptions (Rev 1), removed option 2, which required compiler support, and further refined the library-only solution to the problem.
N2106, Cloning and Throwing Dynamically Typed Exceptions, was a major revision of N2061, Library Exception Propagation Support.
// cloneable demonstation ----------------------------------------------- // #include <boost/shared_ptr.hpp> #include <string> #include <cassert> #include <iostream> // ---------------------------------------------------------------------- // // this would normally be in a header file: class cloneable { public: typedef boost::shared_ptr<cloneable> ptr_type; virtual ptr_type dynamic_clone() const=0; virtual void dynamic_throw() const=0; virtual ~cloneable(){} }; // ---------------------------------------------------------------------- // // this is a user-defined cloneable type: class my_type : public virtual cloneable { public: my_type(const std::string& what_) : _what_(what_) {} ptr_type dynamic_clone() const { return ptr_type(new my_type(*this)); } void dynamic_throw() const { throw *this; } const std::string& what() const { return _what_; } private: std::string _what_; }; // ---------------------------------------------------------------------- // int main() { cloneable::ptr_type p; try { throw my_type("bingo!"); } catch (const cloneable & ex) { p = ex.dynamic_clone(); // save a copy of the exception } // ... try { p->dynamic_throw(); } // rethrow the saved copy catch (const my_type& ex) { // this block should be entered assert(ex.what()=="bingo!"); std::cout << ex.what() << '\n'; } // if the dynamic type was not propagated correctly, this // catch block would be entered and the assert would fire catch (...) { assert(false); } return 0; }
© Copyright Beman Dawes 2006, 2007