2011-10-15 18 views
8

我不问C++异常是否可以通过C代码传播,也不会在发生这种情况时发生什么。我已阅读SO(1,2,3)和中的以下问题。我问如何进行:混合C和C++代码时确保异常传播的机制

  • 避免泄漏对C代码的任何C++异常(这意味着调用C代码之前捕捉在C++土地所有异常)
  • 还能够赶上之外的例外C代码(在更高的C++代码中)。

让我说明了我的想法:

libfoo是一个C库,我想在我的bar C++程序中使用。 libfoo需要我必须提供的回调函数foo_callback。该函数和方法在我的回调使用可能会抛出异常,所以我写了:

void my_callback(void) 
{ 
    try 
    { 
     // Do processing here. 
    } 
    catch(...) 
    { 
     // Catch anything to prevent an exception reaching C code. 
     // Fortunately, libfoo provides a foo_error function to 
     // signal errors and stop processing. 
     foo_error() ; 
    } 
} 

然后我用我的回调,如下图所示:

// The bar program. 
int main() 
{ 
    // Use libfoo function to set the desired callback 
    foo_set_callback(&my_callback) ; 

    // Start processing. This libfoo function uses internally my_callback. 
    foo_process() ; 

    // Check for errors 
    if(foo_ok()) 
    { 
     // Hurray ! 
    } 
    else 
    { 
     // Something gone wrong. 
     // Unfortunately, we lost the exception that caused the error :(
    } 
} 

我要的是能够赶上在main函数中从my_callback中抛出的异常没有例外通过libfoo传播(是的,这是通过C代码实验quantum tunnelling的一种量子异常)。

所以我很想代码使用方法:

void my_callback(void) 
{ 
    try 
    { 
     // Do processing here. 
    } 
    catch(...) 
    { 
     // Catch anything to prevent an exception reaching C code. 
     // We save the exception using (the magic) ExceptionHolder. 
     ExceptionHolder::Hold() ; 

     // Call foo_error function to signal errors and stop processing. 
     foo_error() ; 
    } 
} 

// The bar program. 
int main() 
{ 
    // Use libfoo function to set the desired callback 
    foo_set_callback(&my_callback) ; 

    try 
    { 
     // Start processing. This libfoo function uses internally my_callback. 
     foo_process() ; 

     // Once gone out of the C land, release any hold exception. 
     ExceptionHolder::Release() ; 
    } 
    catch(exception & e) 
    { 
     // Something gone wrong. 
     // Fortunately, we can handle it in some manner. 
    } 
    catch(/*something else */) 
    { 
    } 
    // ... 
} 

考虑以下限制:

  • libfoo是源封闭,用C语言编写,并从供应商处编译格式提供。在图书馆进行的测试表明,例外不能通过它传播。我无法访问源文件,也无法获得支持异常的编译版本。
  • 回调函数广泛使用使用异常的C++代码。所有的错误处理都是围绕异常机制构建的。绝不能简单地使用吞下所有异常的代码。
  • 不涉及多线程。
  • 没有C++ 0x支持。

我的问题:

  • 是它已经被一些图书馆或一些C++魔法(比如boost),甚至的C++ 0x解决了吗?
  • 如果不是,我该如何编写一个适用于任何异常类型的ExceptionHolder?我用C++很舒服,但我还没有找到一种方法来编写一个可靠且易于使用的ExceptionHolder,它适用于任何异常类型。

非常感谢您的任何建议!


编辑:我加一点点实施例外保持/释放机构的响应。所有的评论家或主张都是受欢迎的。

回答

1

编辑:您可以使用fungo,更好地实现我下面所描述的想法。从它的作者:

fungo是一个C++库,是专为我们这些坚持使用旧的C++实现,还不支持std :: exception_ptr。

换句话说,fungo可以让你做出公正的企图在存储并在稍后重新抛出由一个catch(...)块捕获异常。这对于跨线程连接或通过C/C++边界接口传播异常很有用。

我会记住这作为一个答案。


正如我在我的问题提到的,我不能使用的C++ 0x/11的功能,现在(使用新的功能是没有计划现在),我会在这里提出我迄今所做的:

异常有一生跨越了尝试捕捉器区段跨越。为了保存一个例外,一个人必须在堆上创建一个副本。重新抛出异常时我们摆脱了副本。我写了一个例外架接口:

class ExceptionHolderInterface 
{ 
    public : 
     ExceptionHolderInterface(void) ; 
     virtual ~ExceptionHolderInterface(void) ; 

     /* For holding an exception. To be called inside a catch block.*/ 
     virtual void Hold(void) = 0 ; 

     /* For releasing an exception. To be called inside a try block.*/ 
     virtual void Release(void) = 0 ; 
    private : 
} ; 

这是一种独立的阶级。异常类型使用模板介绍:

template<typename ExceptionType> 
class ExceptionHolder : public ExceptionHolderInterface 
{ 
    public : 
     ExceptionHolder(void) ; 
     virtual ~ExceptionHolder(void) ; 

     virtual void Hold(void) 
     { 
      try 
      { 
       throw ; 
      } 
      catch(const ExceptionType & e) 
      { 
       exception.reset(new ExceptionType(e)) ; 
      } 
     } 

     virtual void Release(void) 
     { 
      if(exception.get()) 
      { 
       throw ExceptionType(*exception.get()) ; 
      } 
     } 
    private : 
     std::auto_ptr<ExceptionType> exception ; 

     // declare the copy-constructor and the assignment operator here to make the class non-copyable 
} ; 

我删除了一堆测试/优化/验证的,我不停的主要思想。到目前为止,我们有一种类型的例外持有人,所以我们可以建立一个例外店,可以同时容纳多种类型。

class ExceptionStore 
{ 
    public : 
     ExceptionStore(void) ; 
     ~ExceptionStore(void) 
     { 
      for(Iterator holder = exception_holders.begin() ; holder != exception_holders.end() ; ++holder) 
      { 
       delete (*holder) ; 
      } 
     } 

     // Add an exception type to handle 
     template<typename ExceptionType> 
     void AddExceptionHolder(void) 
     { 
      exception_holders.push_back(new ExceptionHolder<ExceptionType>()) ; 
     } 

     // Try to hold an exception using available holders. Use this inside a catch block. 
     void Hold(void) 
     { 
      Iterator holder = exception_holders.begin() : 
      while(holder != exception_holders.end()) 
      { 
       try 
       { 
        (*holder)->Hold() ; 
        break ; 
       } 
       catch(...) 
       { 
        ++holder ; 
       } 
      } 
     } 

     // Try to release any hold exception. Call this inside a try-block. 
     void Release(void) 
     { 
      Iterator holder = exception_holders.begin() : 
      while(holder != exception_holders.end()) 
      { 
       (*holder++)->Release() ; 
      } 
     } 

    private : 
     std::list<ExceptionHolderInterface *> exception_holders ; 
     typedef std::list<ExceptionHolderInterface *>::iterator Iterator ; 

     // Declare the copy-constructor and the assignment operator here to make the class non-copyable 
} ; 

我可以使用异常店,如下图所示:

// I made a global ExceptionStore just to keep the example simple. 
ExceptionStore exception_store ; 

void callable_from_c_code(void) 
{ 
    // Normally, we should retrieve the exception store in some manner. 

    try 
    { 
     // Do processing here. Exceptions may be thrown. 
    } 
    catch(...) 
    { 
     // Something wrong happened. Let's store the error for later. 
     exception_store.Hold() ; 
    } 

    // Exceptions do not propagate to C code. 
} 

int main(int, char * []) 
{ 
    // First, set the exception types we want to handle. The handling is done in 
    // the same order as below. 
    exception_store.AddExceptionHolder<std::runtime_error>() ; 
    exception_store.AddExceptionHolder<std::logic_error>() ; 
    exception_store.AddExceptionHolder<MyFancyException>() ; 

    // Somehow invoke some C code that uses `callable_from_c_code` 
    use_some_c_library_with_callback(&callable_from_c_code) ; 

    // Handle any caught exception 
    try 
    { 
     exception_holder.Release() ; 
    } 
    catch(std::exception &) 
    { 
     // Something gone wrong ... 
    } 
    catch(MyFancyException &) 
    { 
     // Nothing fancy despite the name. We have problems here ... 
    } 
} 

这是很基本的,有可能是不被这个例子中处理一些意外scenarii。如果使用AddExceptionHolder未声明的类型的例外是罚球,你有两种可能性:

  • 一种基本类型持有人加入到店,所以异常会被捕获并切片,只保留一个副本基本类型。
  • 没有持有人符合例外,它只是丢失。没有任何东西泄漏到C地。

就目前而言,我更喜欢使用这个解决方案能够更测试/使用/验证boost::enable_current_exception的,因为我买不起重构整个C++代码全部包围扔场所boost::enable_current_exception(...)

反正std::exception_ptr似乎是完美的解决方案,我将取代上面的代码,一旦我可以移动到新的C++标准。

4

我相信boost.exception有可能适合于你的目的有用的机制。看到这里的灵感:

http://www.boost.org/doc/libs/1_47_0/libs/exception/doc/tutorial_exception_ptr.html

这似乎是用于通信线程之间的特殊的异常类型,但我认为这是几乎同样的事情 - 从跨线程边界或C传播停止异常代码边界,将其副本存储在指针后面,稍后通过边界另一侧的指针进行检索,并有选择地重新抛出它。

我不知道它是多么可行,使自己的异常从boost的神奇异常类型派生的,但如果我从模具记错与它周围的一年多前,这是相当合理的。

+0

这也许值得一提的是,升压方法反映比较直接的是已被引入到标准库中的C++ 0x解决同一问题的机制。但是,从我所知道的情况来看,它们有点神奇。 – ben

+0

谢谢!我会调查你的提议,因为它似乎很有前途:) – overcoder

+0

在使用'boost :: enable_current_exception'后,我发现它几乎没有灵活性,因为它强制用户用'boost :: enable_current_exception'来包围所有异常所有可能的投掷网站,然后才能使用它。在当前的代码库中,我无法承受这个* major *重构。 – overcoder

1

如果你不想要一个主要的重构,你能够识别可能抛出的异常类型的特定子集吗?

如果是这样,您可以写一个容器来专门存储每个容器的副本,然后从C调用返回时检索它们。这需要能够复制异常类型。

另外,考虑一下各种异常的最终解决是(如果可以的话),并收拾那成在返回某个对象进行处理。这只有在拥有足够的代码库才能知道处理异常可能产生的潜在可能时才有效。

一个氢化物模型将捕获异常,创建基于发现了什么新的异常,然后抛出一个关于从C LIB回报。

+0

谢谢!这基本上是我目前正在做的,但处理特定的异常类型子集是我当前解决方案的弱点。缺少某些情况会破坏可靠性。 – overcoder

+0

不知道我关注。如果库包装器不知道如何处理异常,那么调用代码是否经常这样做?如果没有,一个catch(...)被映射到一个单一类型的rethrow会给你你想要的定义的行为。 – Keith

+0

好主意。坚持例外使我感觉像使用错误的工具进行流量控制。我的代码需要重新思考... – overcoder

2

在C++ 11中,您可以在回调中使用current_exception()来设置一个类型为exception_ptr的变量,然后当您的调用代码被库使用rethow_exception()通知错误时。

这是用于在C++ 0x中的线程间传播异常的机制。

例子:


void foo();      // C++ 
extern "C" void bar(void *exp); // C 
void baz(void *exp);    // C++ 

void foo() { 
    std::exception_ptr exp; 
    bar(&exp); 
    if (exp) { 
     std::rethrow_exception(exp); 
    } 
} 

extern "C" void bar(void *exp) { 
    baz(exp); 
} 

void baz(void *exp) { 
    try { 
     // some code that might throw an exception 
     // ... 
    } catch (...) { // catch all exceptions, so as to avoid leaking any into the calling C code. 
     // capture the exception and make it available to the C++ code above the C code. 
     static_cast<std::exception_ptr*>(exp) = std::current_exception(); 
    } 
}