在变量中存储异常的正确方法

时间:2010-06-02 18:42:07

标签: c++ exception

我有一个API,内部有一些错误报告的例外。基本结构是它有一个继承自std::exception的根异常对象,然后它会抛出一些子类。

由于捕获在一个库或线程中抛出的异常并将其捕获到另一个库或线程中会导致未定义的行为(至少Qt会抱怨它并且在许多情况下不允许它)。我想在函数中包装库调用,这些函数将返回状态代码,如果发生异常,则包含异常对象的副本。

存储异常(具有多态行为)以供以后使用的最佳方法是什么?我相信c ++ 0x future API会使用这样的东西。那么最好的方法是什么?

我能想到的最好的方法是在每个异常类中都有一个clone()方法,它将返回一个指向同一类型异常的指针。但这并不是非常通用,根本不涉及标准例外。

有什么想法吗?

编辑:似乎c ++ 0x会有a mechanism for this。它被描述为“图书馆魔术”。这是否意味着不需要c ++ 0x的任何语言功能?如果没有,是否有任何与c ++ 03兼容的实现?

编辑:看起来好消息有implementation of exception copying。对于任何非boost::copy_exception答案,我都会保持开放的问题。

编辑:解决j_random_hacker关于异常的根本原因的问题,即内存不足错误。对于这个特定的库和一组例外,情况并非如此。从根异常对象派生的所有异常表示由无效用户输入引起的不同类型的解析错误。与内存相关的异常只会导致抛出std::bad_alloc,并单独解决。

5 个答案:

答案 0 :(得分:4)

你有什么我认为最好的,唯一的答案。您不能保留对原始异常的引用,因为它将留下范围。你只需要复制它,唯一的通用方法是使用像clone()这样的原型函数。

对不起。

答案 1 :(得分:2)

从C ++ 11开始,可以使用std :: exception_ptr完成此操作。

(我在使std :: thread可中断的类中使用此方法,只要基础线程实现是POSIX线程即可。处理可能在用户代码中引发的异常-如果将它们引发到实现的某些关键部分-我使用std :: exception_ptr存储异常,然后在关键部分完成后将其抛出。)

要存储该异常,请捕获该异常并将其存储在ptr变量中。

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

然后,您可以将eptr传递到您喜欢的任何地方,甚至传递到其他线程中(根据文档-我自己还没有尝试过)。当需要再次使用(即抛出)它时,您将执行以下操作:

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

如果要检查异常,只需捕获它即可。

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

答案 2 :(得分:1)

你被允许抛出任何东西,包括指针。你可以做这样的事情:

throw new MyException(args);

然后在异常处理程序中存储捕获的指针,它将是完全多态的(下面假设MyException派生自std::exception):

try {

   doSomething(); // Might throw MyException*

} catch (std::exception* pEx) {

   // store pEx pointer
}

当你这样做时,你必须要小心内存泄漏,这就是为什么通常使用throw-by-value和catch-by-reference。

有关按指针的更多信息:http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

答案 3 :(得分:0)

捕获在一个库中抛出的异常并将其捕获到另一个库中的原因可能导致未定义的行为,即这些库可以与不同的运行时库链接。如果你将从函数中返回异常而不是抛出它,你将无法避免这个问题。

答案 4 :(得分:0)

我的实用程序库有一个AnyException类,与没有转换支持的boost::any基本相同。相反,它有一个Throw()成员,它会抛出存储的原始对象。

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;
};

这是一种简化,但这是基本框架。我的实际代码禁用了复制,而是移动了语义。如果需要,您可以轻松地向Clone添加虚拟ExceptionBase方法...因为Exception知道对象的原始类型,它可以将请求转发到实际的复制构造函数,您立即支持所有可复制类型,而不仅仅是拥有Clone方法的类型。

当设计它时,它不是用于存储捕获的异常...一旦抛出异常,它就像正常一样传播,因此不考虑内存不足的情况。但是,我想你可以在对象中添加std::bad_alloc的实例,并将其直接存储在这些情况中。

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;
 };

我实际上根本没有测试过第二位......我可能会遗漏一些明显会阻止它工作的东西。

相关问题