std::rethrow_exception and thrown exception type

2020-08-17 13:40发布

问题:

In a library I have a hierarchy of exceptions (RuntimeException -> RecognitionException -> NoViableAltException). During execution, a NoViableAltException is thrown, which is caught as below and an exception pointer is created from that:

try {
   //code that throws NoViableAltException
} catch (RecognitionException &re) {
  std::exception_ptr e = std::make_exception_ptr(re);
  reportError(e);
}

The value of e is used in other places, irrelevant for this question. In reportError() I actually handle the error:

void reportError(std::exception_ptr e) {
...
  try {
    std::rethrow_exception(e);
  } catch (NoViableAltException &ne) {
    reportNoViableAlternative(recognizer, ne);
  } catch (InputMismatchException &ne) {
    reportInputMismatch(recognizer, ne);
  } catch (FailedPredicateException &ne) {
    reportFailedPredicate(recognizer, ne);
  } catch (RecognitionException &ne) {
    recognizer->notifyErrorListeners(ne.getOffendingToken(), ne.what(), e);
  }
}

and here's my problem: when I rethrow e, the NoViableAltException branch is not taken, but that of the RecognitionException (the last one). That is surprising and I wonder why this happens. I also tried to catch NoViableAltException*, to no avail. What is the correct approach to catch the individual exception types?

回答1:

From documentation for std::make_exception_ptr:

Creates an std::exception_ptr that holds a reference to a copy of e.

Unfortunately, copying e means you get object slicing (which @Mohamad Elghawi points out is also more prominently mentioned later on that page). When you call std::make_exception_ptr<RecognitionException>, it will hold a copy of a RecognitionException, not any derived class.

But you don't need exception_ptr at all here. Even though reportError does not have the try...catch in scope, you can still use throw; to re-throw the current exception.

#include <stdio.h>

struct A { virtual ~A() = default; };
struct B : A { };

void reportError() {
  try {
    throw;
  }
  catch (B &) {
    puts("caught B");
  }
  catch (A &) {
    puts("caught A");
  }
}

int main() {
  try {
    throw B();
  }
  catch (A &) {
    reportError();
  }
}


回答2:

Don't use make_exception_ptr; that's something different (it creates a new exception pointer with a deduced exception type, and your code ends up slicing the caught exception object). Instead, you want to capture the current exception:

catch (RecognitionException &)
{
     std::exception_ptr e = std::current_exception();
     // ...
}


标签: c++ c++11