correct way to store an exception in a variable

2020-08-23 01:27发布

问题:

I have an API which internally has some exceptions for error reporting. The basic structure is that it has a root exception object which inherits from std::exception, then it will throw some subclass of that.

Since catching an exception thrown in one library or thread and catching it in another can lead to undefined behavior (at least Qt complains about it and disallows it in many contexts). I would like to wrap the library calls in functions which will return a status code, and if an exception occurred, a copy of the exception object.

What is the best way to store an exception (with it's polymorphic behavior) for later use? I believe that the c++0x future API makes use of something like this. So what is the best approach?

The best I can think of is to have a clone() method in each exception class which will return a pointer to an exception of the same type. But that's not very generic and doesn't deal with standard exceptions at all.

Any thoughts?

EDIT: It seems that c++0x will have a mechanism for this. It is described as "library magic". Does that mean that is doesn't require any of the language features of c++0x? if not, are there any implementations which are compatible with c++03?

EDIT: Looks like boost has an implementation of exception copying. I'll keep the question open for any non boost::copy_exception answers.

EDIT: To address j_random_hacker's concerns about the root cause of the exception being an out of memory error. For this particular library and set of exceptions, this is not the case. All exceptions derived from the root exception object represent different types of parsing errors caused by invalid user input. Memory related exceptions will simply cause a std::bad_alloc to be thrown which is addressed separately.

回答1:

You have what would be what I think is your best, only answer. You can't keep a reference to the original exception because it's going to leave scope. You simply have to make a copy of it and the only generic way to do that is with a prototype function like clone().

Sorry.



回答2:

As of C++11, this can be done using std::exception_ptr.

(I use this in a class that makes an std::thread interruptible provided that the underlying thread implementation is a POSIX thread. To handle exceptions that may be thrown in the user's code - which causes problems if they are thrown in a certain critical section of my implementation - I store the exception using std::exception_ptr, then throw it later once the critical section has completed.)

To store the exception, you catch it and store it in the ptr variable.

std::exception_ptr eptr;
try {
    ... do whatever ...
} catch (...) {
    eptr = std::current_exception();
}

You can then pass eptr around wherever you like, even into other threads (according to the docs - I haven't tried that myself). When it is time to use (i.e. throw) it again, you would do the following:

if (eptr) {
    std::rethrow_exception(eptr);
}

If you want to examine the exception, you would simply catch it.

try {
    if (eptr) {
        std::rethrow_exception(eptr);
    }
} catch (const std::exception& e) {
    ... examine e ...
} catch (...) {
    ... handle any non-standard exceptions ...
}


回答3:

You're allowed to throw anything, including pointers. You could always do something like this:

throw new MyException(args);

And then in the exception handler store the caught pointer, which will be fully polymorphic (below assuming that MyException derives from std::exception):

try {

   doSomething(); // Might throw MyException*

} catch (std::exception* pEx) {

   // store pEx pointer
}

You just have to be careful about memory leaks when you do it this way, which is why throw-by-value and catch-by-reference is normally used.

More about catch-by-pointer: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8



回答4:

The reason why catching an exception thrown in one library and catching it in another can lead to undefined behavior is that these libraries could be linked with different Runtime libraries. If you will return exception from a function instead of throwing it you will not avoid that problem.



回答5:

My utility library has an AnyException class that is basically the same as boost::any without the casting support. Instead, it has a Throw() member that throws the original object stored.

struct AnyException {
  template<typename E>
  AnyException(const E& e) 
    : instance(new Exception<E>(e))
  { }

  void Throw() const {
    instance->Throw();
  }

private:
  struct ExceptionBase {
    virtual void Throw() const =0;
    virtual ~ExceptionBase() { }
  };

  template<typename E>
  struct Exception : ExceptionBase {
    Exception(const E& e)
      : instance(e)
    { }

    void Throw() const {
      throw std::move(instance);
    }

  private:
    E instance;
  };
  ExceptionBase* instance;
};

This is a simplification, but that's the basic framework. My actual code disables copying, and has move semantics instead. If needed, you can add a virtual Clone method to the ExceptionBase easily enough... since Exception knows the original type of the object, it can forward the request onto the actual copy constructor, and you immediately have support for all copiable types, not just ones with their own Clone method.

When this was designed, it was not meant for storing caught exceptions... once an exception was thrown, it propagated as normal, so out-of-memory conditions were not considered. However, I imagine you could add an instance of std::bad_alloc to the object, and store it directly in those situations.

struct AnyException {
   template<typename E>
   AnyException(const E& e) {
      try {
          instance.excep = new Exception<E>(e);
          has_exception = true;
      } catch(std::bad_alloc& bad) {
          instance.bad_alloc = bad;
          bas_exception = false;
      }
   }

   //for the case where we are given a bad_alloc to begin with... no point in even trying
   AnyException(const std::bad_alloc& bad) {
     instance.bad_alloc = bad;
     has_exception = false;
   }

   void Throw() const {
     if(has_exception)
         instance.excep->Throw();
     throw instance.bad_alloc;
   }

 private:
   union {
     ExceptionBase* excep;
     std::bad_alloc bad_alloc;
   } instance;
   bool has_exception;
 };

I haven't actually tested that second bit at all... I might be missing something glaringly obvious that will prevent it from working.



标签: c++ exception