我不是在问C ++异常是否安全通过C代码传播,也不会在发生这种情况时发生什么。我在SO(1,2,3)和this FAQ中阅读了以下问题。我问如何继续:
让我说明一下我的想法:
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语言编写,并以供应商的编译格式提供。在图书馆进行的测试表明,例外不能通过它传播。我无权访问源文件,也无法获得支持异常的编译版本。我的问题:
boost
),甚至c ++ 0x解决了?非常感谢任何建议!
编辑:我添加了一个带有异常保持/释放机制的实现的响应。欢迎所有评论家或主张。
答案 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
声明的类型的异常,则有两种可能性:
目前,我更喜欢将此解决方案用于更多经过测试/使用/验证的boost::enable_current_exception,因为我无法重构整个C ++代码以围绕具有boost::enable_current_exception(...)
的所有投掷网站。
无论如何,std::exception_ptr
似乎是完美的解决方案,一旦我转向新的C ++标准,我将替换上面的代码。