【问题标题】:Code reuse in exception handling异常处理中的代码重用
【发布时间】:2010-10-25 05:45:31
【问题描述】:

我正在为一些用 C++ 编写的功能开发一个 C api,我想确保不会从任何导出的 C 函数中传播异常。

简单的方法是确保每个导出的函数都包含在:

try {
   // Do the actual code
} catch (...) {
   return ERROR_UNHANDLED_EXCEPTION;
}

假设我知道 C++ 代码中经常遗漏的一个异常是 std::bad_alloc,我想特别对待它,我会写这样的东西:

try {
   // Run the actual code
} catch (std::bad_alloc& e) {
   return ERROR_BAD_ALLOC;
} catch (...) {
   return ERROR_UNHANDLED_EXCEPTION;
}

是否有可能以某种巧妙的方式对其进行分解,以便在不为每个导出函数周围的异常处理程序添加新的 catch 语句的情况下全局不同地处理某些错误?

我知道这可以使用预处理器来解决,但在走这条路之前,我会确保没有其他方法可以做到这一点。

【问题讨论】:

    标签: c++ c exception-handling


    【解决方案1】:

    永远不要使用catch(...),除非您计划或多或少地立即重新投掷。您肯定会丢失任何可能需要帮助您找出错误原因的错误信息。

    我更喜欢您的第二种方案 - 捕获一组已知的异常,理想情况下,因为它们是您的代码将抛出的唯一异常,并让其余的通过 - 允许应用程序崩溃可能是最好的做法,因为您调用了未知行为,最好“负责任地崩溃”。

    【讨论】:

    • 在这种情况下,我认为使用 catch(...) 是正确的做法。在 C++ 中,即使不是不可能,也很难预测所有可以抛出的异常,但他必须防止它们传播到他的 C 代码中。
    • 如果你这样做了,那么就打算输入一堆糟糕的错误报告,几乎没有关于导致问题的原因的信息,也没有办法获得更多信息
    • 所以你宁愿程序崩溃,因为你依赖的一些不起眼的库在两次删除时决定它会抛出 MyWeirdError?我想我宁愿在日志中看到“操作 XXX 的未识别问题”。
    • 您宁愿崩溃、内存损坏、死锁等待发生、忘记关闭文件还是解锁互斥锁?您可能已经通过丢失您将获得的关于导致它的唯一信息来避免对不稳定的感知。
    • 那么您的解决方案是什么?恐怕不会发生崩溃 - 与日志条目相比,它提供的信息更少。
    【解决方案2】:

    怎么样:

    try{
        //Your code here
    } catch(std::exception e)
    {
       return translateExceptionToErrorCode(e);
    } catch(...)
    {
       return UNKNOWN_EXCEPTION_THROWN;
    }
    

    【讨论】:

    • 通常你会通过引用捕获异常,以避免不必要的复制
    【解决方案3】:

    对于所有可能的异常,您只能使用一个处理函数,并从每个或您的 API 实现函数调用它,如下所示:

    int HandleException()
    {
        try 
        {
            throw;
        }
    
        // TODO: add more types of exceptions
    
        catch( std::bad_alloc & ) 
        {
           return ERROR_BAD_ALLOC;
        }
        catch( ... )
        {
            return ERROR_UNHANDLED_EXCEPTION;
        }
    }
    

    并且在每个导出的函数中:

    try
    {
        ...
    }
    catch( ... )
    {
        return HandleException();
    }
    

    【讨论】:

    • 在实际代码中不要忘记通过引用捕获异常:catch(std::bad_alloc &)
    • HandleException 会看到外面抛出的异常吗?
    • 你需要解释重投。对于很多开发人员(尤其是初学者)来说,这将是一个非常新的概念。
    • @LokiAstari 这可能会成为一个很棒的帖子,我也不知道这种重新抛出机制,所以我对它非常感兴趣。
    • 尝试从HandleException返回struct fake_almost_anything { template<class T> operator T() { return *(T*)nullptr; },而不是int。在 C++11 中将其标记为[[noreturn]]。
    【解决方案4】:

    在语言边界松散错误信息将是一种耻辱。您真的应该尝试将所有异常转换为可从 C 中使用的错误代码。

    您如何做到这一点实际上取决于您的异常类是什么样的。如果您控制异常类层次结构,则可以确保每个类都使用虚拟方法提供翻译。如果不是,您可能仍然会发现使用翻译函数并测试它接收到的“std::exception”派生异常的类型以将其转换为错误代码是可行的,就像 Jem 建议的那样(记住:抛出的异常会伤害无论如何性能,所以不用担心翻译速度很慢)。

    【讨论】:

    • 把异常翻译成错误码似乎正是他在做的事!
    【解决方案5】:

    Jem 答案比这个解决方案简单一点。但是可以使用模板来代替预处理器宏的使用。像这样的东西(你可以做更多的改进):

    template <class T, void (T::*FUNC)()>
    class CatchWrapper
    {
    public:
    
        static void WrapCall(T* instance)
        {
            try
            {
                (instance->*FUNC)();
            }
            catch (std::bad_alloc&)
            {
                // Do Something 1
            }
            catch (std::exception& e)
            {
                // Do Something 2
            }
            catch (...)
            {
                // Do Something 3
            }
        }
    };
    
    
    class Foo
    {
    public:
        void SomeCall()
        {
            std::cout << "Do Something" << std::endl;
        }
    };
    
    
    int main(int argc, char* argv[])
    {
        Foo i;
        CatchWrapper<Foo, &Foo::SomeCall>::WrapCall(&i);
        return 0;
    }
    

    【讨论】:

    • 这也是我首先想到的,(实际上是我在一些项目中使用的)。但是从我的角度来看,杰姆的答案更干净。虽然它不会打扰我,但有时模板的使用会打扰其他程序员.. :/
    【解决方案6】:

    已经有一个很好的答案。但仅供参考,它被称为“异常调度程序”成语,请参阅C++ FAQ。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-23
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多