Doc. no.   N2229==07-0089
Date:        2007-05-05
Project:     Programming Language C++
Reply to:   Beman Dawes <bdawes@acm.org>

Cloning and Throwing Dynamically Typed Exceptions (Rev 1)
(Library-only support for exception propagation from threads)

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   

Preface

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.

Introduction

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.

Proposed changes to the working paper

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

Rationale for mixin approach

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.

Impact on the Standard

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.

Impact on existing code

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.

Implementation experience using the proposed mixin

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.

Acknowledgements

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.

Revision history

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.

Demonstration program

// 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