C ++抛出异常(需要建议)

时间:2016-09-03 19:15:14

标签: c++ exception-handling

我正在阅读bjarne Stroustrup撰写的C ++编程语言,特别是有关异常安全和RAII编程习惯的章节。我熟悉RAII,但没有抛出异常。实际上,我没有经常看到throw关键字的使用。当我研究标准模板库以了解模板机制和RAII习语时,我很谨慎地看到它(矢量,红/黑树......)。

如果我要使用throw关键字,我会倾向于一直使用它。因此,这种语法意味着使用try-catch子句,这使得代码看起来很难看。

您如何看待这个?是否有更好的技术来处理异常?或者我应该绝对使用throw?

感谢您的回复。

3 个答案:

答案 0 :(得分:2)

首先,这些链接可能会有所帮助。 Link 1 Link 2

大多数情况下,C ++异常更适合任何替代方案。实际上,我认为它使代码更漂亮。我会举个例子,希望你能理解。

假设我必须加载一个模型,并准备好渲染(我知道你可能不熟悉图形编程,但你应该明白这一点)。这意味着我有一个很大的功能,或者至少是一个大功能,可以调用其他几个较小的功能。整个过程包括打开模型文件,读取所有坐标,打开材质文件,为材质分配内存,打开纹理文件,为纹理分配内存,加载纹理等等。很多东西。

但有时候,在最后,我可能会遇到一个错误,我无法找到其中一个纹理文件,或者无法打开它。怎么处理?有小文件打开函数返回一个值来检查它的纹理加载函数,实现发生了什么不好的事情,然后返回到读取主模型文件的函数等?你认为所有返回,检查和释放每个点的资源看起来都很丑陋吗?

这是一种更好的方法:将 load_model()函数包装在try块中,只需在需要它们的地方插入一些throw语句。拥有所有"免费记忆" catch部分中的代码。它会看起来更清洁,也不太可能出错。

我希望你理解这个想法。如果您有任何疑问,请询问他们。

答案 1 :(得分:2)

例外情况非常适合处理exceptional cases

  • 阻止构造函数完成其工作的错误。
  • 预计不会发生的错误。一个典型的例子是内存分配问题
  • 可能会级联的错误,因为它们表明存在明显不利的情况

但是当错误不是异常时异常should not be used,即预期会定期发生(例如循环直到条件发生)。在这种情况下,特殊的返回值是更好的选择。

原因有三个:

  • 概念:“例外”一词强烈暗示特殊情况。
  • 性能:现代编译器进入试用集团几乎没有明显的性能影响:它是一种非常便宜的保护。但是当你抛出时,显然有一个堆栈展开努力比返回一些值要昂贵得多(“更昂贵”然而仍然比显示消息更快......一个数量级:大约7μs扔掉我原来的i7 cpu,相比之下纳秒等级的回报
  • 健壮性:如果作为堆栈展开的一部分而被销毁的对象抛出异常,则突然处理程序terminate()的异常。这种情况非常不可能发生。但是,如果您滥用异常处理并在几乎正常的情况下使用它作为返回的替代,则增加在这种情况下成为一天的可能性,除非you'd take extreme care

答案 2 :(得分:1)

当我有一个必须返回的功能时,我只使用throw,我可以。当我返回指针时,空指针就足够了,但是当返回引用时,你什么都不能返回,所以我扔了。其中一部分,我从不扔,而且我从来没有抓过,因为你说的原因。

对于带有错误处理的C ++,还有更好的方法。首先,有后续C ++版本的函数契约。它允许你这样做:

struct Database {
    SomeResult query(std::string) [[expect: connected]] {
        // ...
    }

private:
    bool connected = false;
};

错误处理程序是可自定义的,因此可能是异常,可能是std::terminate

也有std::expected提案。这是一个实用程序,您现在可以使用boost实现。

它允许函数返回结果,或返回错误。然后,函数的用户可以以比catch更漂亮的方式正确处理它。考虑这个例子,取自提案:

  

根据预期,我们不需要使用例外,我们可以使用   std :: error_condition比内省更容易内省   std :: exception_ptr如果我们想要使用错误。为了...的目的   在这个例子中,我们使用以下枚举(样板代码   关于std :: error_condition未显示):

enum class arithmetic_errc
{
    divide_by_zero, // 9/0 == ?
    not_integer_division // 5/2 == 2.5 (which is not an integer)
};
     

使用预期,代码变为:

expected<double,error_condition> safe_divide(double i, double j)
{
    if (j==0) return make_unexpected(arithmetic_errc::divide_by_zero); // (1)
    else return i / j; // (2)
}

在提案中,它表明您可以更改用户功能:

  

例如,基于异常的函数i + j / k是:

double f1(double i, double j, double k)
{
    return i + safe_divide(j,k);
}
     

但变得使用预期:

expected<double, error_condition> f1(double i, double j, double k)
{
    auto q = safe_divide(j, k)
    if(q) return i + *q;
    else return q;
}

稍后会显示您可以使用std::expected::map编写此缩短版本:

expected<double, error_condition> f1(double i, double j, double k)
{
    return safe_divide(j, k).map([&](double q){
        return i + q;
    });
}

您可以在此处阅读这两项提案:Simple Contact for C++std::expected proposal