混合C和C ++代码时确保异常传播的机制

时间:2011-10-15 23:21:44

标签: c++ c exception

我不是在问C ++异常是否安全通过C代码传播,也不会在发生这种情况时发生什么。我在SO(123)和this FAQ中阅读了以下问题。我问如何继续:

  • 避免将任何C ++异常泄露给C代码(这意味着在调用C代码之前捕获C ++域中的所有异常)
  • 还能够捕获C代码之外的异常(在更高的C ++代码中)。

让我说明一下我的想法:

Say 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 :(
    }
}

我想要的是能够捕获my_callback函数中从main抛出的异常,而不会通过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,它适用于任何异常类型。

非常感谢任何建议!


编辑:我添加了一个带有异常保持/释放机制的实现的响应。欢迎所有评论家或主张。

4 个答案:

答案 0 :(得分:4)

我相信boost.exception有一种机制可以适应你的目的。看到这里寻找灵感:

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

它似乎是用于在线程之间传递它们的特殊异常类型,但我认为它几乎是一样的 - 阻止异常传播到线程边界或C代码边界,将它的副本存放在一个指针,然后通过边界另一侧的指针检索它,并可选择重新抛出它。

我不确定让你自己的异常派生自boost的神奇异常类型是多么可行,但是如果我在大约一年前的工具中正确记得它,那就相当合理了。

答案 1 :(得分:4)

在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();
    }
}

答案 2 :(得分:1)

如果您不适合使用主要的重构,您是否能够识别可能引发的异常类型的特定子集?

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

或者,考虑各种异常的最终解决方案(如果可以的话),并将其打包到某个对象中以便在返回时进行处理。这仅适用于您拥有足够的代码库以了解异常可能产生的处理方式。

一个氢化模型会捕获异常,根据找到的内容创建一个新异常,然后从C lib返回时抛出异常。

答案 3 :(得分:1)

编辑:你可以使用fungo,更好地实现我在下面描述的想法。来自作者:

  

fungo是一个C ++库,专为那些使用尚未支持std :: exception_ptr的旧C ++实现的人而设计。

     

换句话说,fungo允许你公平地尝试存储并随后重新抛出catch(...)块捕获的异常。这对于跨线程连接或通过C / C ++边界接口传播异常非常有用。

我会将此标记为答案。


正如我在我的问题中所提到的,我暂时不能使用C ++ 0x / 11功能(暂时不使用新功能),我将在此处介绍到目前为止我所做的工作:

异常的生命周期跨越try-catcher块。为了保存异常,必须在堆上创建一个副本。我们在重新抛出异常时摆脱了副本。我写了一个异常持有者界面:

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

这是非常基本的,可能会有一些本例未处理的意外情况。如果抛出一个未使用AddExceptionHolder声明的类型的异常,则有两种可能性:

  • 基础类型的持有者被添加到商店,因此异常将被捕获并切片,只保留基本类型的副本。
  • 没有持有人符合例外情况,它就会丢失。没有任何东西泄漏到C地。

目前,我更喜欢将此解决方案用于更多经过测试/使用/验证的boost::enable_current_exception,因为我无法重构整个C ++代码以围绕具有boost::enable_current_exception(...)的所有投掷网站。

无论如何,std::exception_ptr似乎是完美的解决方案,一旦我转向新的C ++标准,我将替换上面的代码。