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

ove*_*der 8 c c++ exception

我不是在问C++异常是否安全通过C代码传播,也不会在发生这种情况时发生什么.我已经在SO(阅读下面的问题1,2,3)和本FAQ.我问如何继续:

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

让我说明一下我的想法:

Say libfoo是一个C库,我想在我的barC++程序中使用它.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() ;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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 :(
    }
}
Run Code Online (Sandbox Code Playgroud)

我要的是能够赶上从抛出的异常my_callbackmain功能,而无需异常传播通过libfoo(是的,这是一种量子异常的实验量子隧道通过C代码).

所以我喜欢使用的代码:

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 */ )
    {
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

鉴于以下限制:

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

我的问题:

  • 它是否已经被某些库或某些C++魔术(如boost),甚至c ++ 0x解决了?
  • 如果没有,我如何编写一个适用于任何异常类型的ExceptionHolder?我很喜欢C++,但我还没有找到一种方法来编写一个可靠且易于使用的ExceptionHolder,它可以处理任何异常类型.

非常感谢任何建议!


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

ove*_*der 2

编辑:您可以使用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 :
} ;
Run Code Online (Sandbox Code Playgroud)

这是一个与类型无关的类。使用模板引入异常类型:

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
} ;
Run Code Online (Sandbox Code Playgroud)

我删除了一堆测试/优化/验证,保留了主要思想。到目前为止,我们已经有了一种类型的异常持有者,因此我们可以构建一个可以同时持有多种类型的异常存储。

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
} ;
Run Code Online (Sandbox Code Playgroud)

我可以使用异常存储,如下所示:

// 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 ...
    }
}
Run Code Online (Sandbox Code Playgroud)

这是非常基本的,可能有一些意想不到的场景无法通过此示例处理。AddExceptionHolder如果抛出未使用声明的类型的异常,则有两种可能性:

  • 基本类型的持有者被添加到存储中,因此异常将被捕获并切片,仅保留基本类型的副本。
  • 没有一个持有者适合例外情况,而且它只是丢失了。没有任何事情泄露到C地。

目前,我更喜欢使用此解决方案,而不是经过更多测试/使用/验证的boost::enable_current_exception,因为我无法重构整个 C++ 代码以用boost::enable_current_exception(...).

不管怎样,这std::exception_ptr似乎是完美的解决方案,一旦我可以转向新的 C++ 标准,我将替换上面的代码。