在C ++非空函数中使用throw替换return

时间:2019-02-15 16:19:36

标签: c++ exception-handling

在C ++函数中,将return替换为throw是一个好习惯吗?例如,我有以下代码

// return indices of two numbers whose sum is equal to target
vector<int> twoSum(vector<int>& nums, int target) {
    for(int i=0; i<nums.size()-1; ++i)
        for(int j=i+1; j<nums.size(); ++j)
        {
            if(nums[i] + nums[j] == target) return vector<int>{i, j};
        }
    // return vector<int>{};
    throw "no solution";
}

上面的代码与我的GCC 7.2一起编译。

10 个答案:

答案 0 :(得分:41)

  

在C ++函数中,用return代替return是一个好习惯吗?

返回通常不能用抛出 代替。

在例外情况下,您什么也没有要返回的东西,抛出异常可能是退出该函数的有效方法。

是否是“良好实践”,什么情况是“例外”是主观的。例如,对于您这样的搜索功能,可能没有解决方案也就不足为奇了,我会认为抛出不合适。

投掷通常还有其他选择。将您的算法与类似std::string::find的算法进行比较,该算法返回子字符串开头的索引。如果子字符串不存在,它将返回“非值” std::string::npos。您可以执行相同的操作,并在未找到结果时决定返回索引-1。还有一种通用方法可以将非值表示形式添加到类型中,在这种情况下,不能为以下目的保留现有表示形式:std::optional

P.S。向量可能不是返回一对数字的好选择。 std::pair可能更好,或者如果您对数字有好的称呼,则可以选择自定义类。

答案 1 :(得分:29)

此答案的概念来自Bjarne Stroustrup的C ++编程语言。

缩短答案

是的,可以将异常抛出用作返回值方法。以下是二叉树搜索功能的示例:

void fnd(Tree∗ p, const string& s)
{
    if (s == p−>str) throw p; // found s
    if (p−>left) fnd(p−>left,s);
    if (p−>right) fnd(p−>right,s);
}


Tree∗ find(Tree∗ p, const string& s)
{
    try {
       fnd(p,s);
    }
    catch (Tree∗ q) {
        // q->str==s
        return q;
    }
    return 0;
}

但是,应避免使用它,因为:

  • 它们使您可以将错误代码与“常规代码”分开,从而使您的程序更具可读性,可理解性和可管理性。如果将它们用作返回方法,则不再适用。
  • 效率可能较低,因为异常实现依赖于将其用作错误处理方法的假设。

除此之外,还有其他限制:

  • 例外必须是可复制的类型
  • 异常只能处理同步事件
  • 在时间紧迫的系统中应避免使用它们
  • 在大型的旧程序中应避免使用它们,在这些程序中,资源管理是一团糟(使用裸指针,新闻和删除对免费存储进行非系统地管理),而不是依赖诸如资源句柄(字符串向量)之类的系统方案。

更长的答案

异常是抛出的代表错误发生的对象。它可以是任何可以复制的类型,但强烈建议仅使用为此目的专门定义的用户定义类型。异常允许程序员将错误处理代码与“普通代码”明确分开,从而使程序更具可读性。

首先,异常用于管理同步事件,而不是异步事件。这是第一个限制。

人们可能会认为异常处理机制只是另一种控制结构,这是将值返回给调用方的另一种方法。

这有一定的魅力,但应避免,因为它可能会引起混乱和效率低下。 Stroustrup建议:

  

尽可能坚持使用“异常处理是错误的   处理”视图。完成后,代码分为两类:   普通代码和错误处理代码。这使代码更加   可以理解的此外,异常的实现   基于以下假设对机制进行了优化:   模型是异常使用的基础。

因此基本上应该避免使用异常来返回值,因为

  • 例外实现的优化是假设它们用于错误处理而不是用于返回值,因此它们可能效率不高;
  • 它们允许将错误代码普通代码分开,从而使代码更具可读性和可理解性。 任何有助于维护清晰的错误模型和错误处理方式的东西都应该保留

有些程序出于实际或历史原因不能使用异常(错误处理也不例外)

  • 嵌入式系统中时间紧迫的组件,必须保证在指定的最大时间内完成操作。在缺少能够准确估计异常从throw传播到catch的最大时间的工具的情况下,必须使用其他错误处理方法。
  • 一个大型的旧程序,其中的资源管理是一团糟(使用裸指针newsdelete非系统地管理免费存储),而不是依靠诸如资源句柄( string s vector s)。

在上述情况下,首选传统的前置例外方法。

答案 2 :(得分:19)

returnthrow有两个不同的用途,不应视为可互换。当您有有效的结果发送回呼叫者时,请使用return。另一方面,发生某些异常行为时,请使用throw。通过使用标准库中的函数并记下它们何时抛出异常,您可以了解其他程序员如何使用throw

答案 3 :(得分:15)

除了其他答案,还具有性能:与if子句相比,捕获异常会导致运行时开销(请参见this answer)。

(当然,我们谈论的是微秒……这是否相关取决于您的特定用例。)

答案 4 :(得分:10)

当函数无法满足其后置条件时,应引发异常。 (某些函数在不满足其前提条件时也可能会引发异常;这是我在这里不涉及的另一主题。)因此,如果您的函数必须从向量求和中返回一对整数到给定目标,则它别无选择,只能在找不到目标时抛出异常。但是,如果该函数的合同允许它返回一个值,表明它无法找到这样的对,则它不应抛出异常,因为它具有履行合同的能力。

在您的情况下:

  • 当找不到两个求和到给定目标的整数时,可以使函数返回一个空向量。然后,它永远不需要抛出异常。
  • 或者,您可以使函数返回std::optional<std::pair<int, int>>。它永远不需要抛出异常,因为当找不到合适的对时,它可以返回一个空的可选内容。
  • 但是,如果使它返回std::pair<int, int>,则它应该引发异常,因为在找不到合适的对时,没有明智的返回值。

通常,C ++程序员更喜欢编写不需要抛出异常的函数来报告仅“令人失望”的内容,例如搜索失败,这些很容易在本地预期和处理。返回值而不是引发异常的优点在其他地方进行了广泛的讨论,因此我在这里不再赘述。

因此,通过抛出异常来声明“失败”通常仅限于以下情况:

  • 该函数是一个构造函数,它根本无法建立其类的不变式。为了确保调用代码看不到损坏的对象,必须抛出异常。
  • 出现“异常”情况,应在比呼叫者更高的级别上进行处理。呼叫者可能不知道如何从该状况中恢复。在这种情况下,使用异常机制可使调用者不必在异常情况发生时就去弄清楚如何进行操作。

答案 5 :(得分:8)

您提到的不是好的编程习惯。在生产级代码中,用return代替throw语句是不可接受的,尤其是对于自动测试平台,该平台会生成所有异常的列表,以此作为证明或反证某些功能的一种方式。在设计代码时,您必须考虑软件测试人员的需求。 throw根本不能与return互换。 throw用于表示由于某些意外现象而发生了程序错误return用于表示方法完成。通常使用return来传输错误代码,但是返回值不会像throw那样导致程序被中断。另外,如果处理不正确,throw可以终止程序。

从本质上讲,在方法中检测到重大错误时,最好使用throw,但如果未检测到此类错误,则应始终返回干净的返回值。 您可以替换吗?也许,也许它将在逻辑上起作用……但这不是一个好习惯,当然也不是一种流行的实现方式。

答案 6 :(得分:7)

否,throw不是return的良好语义替代。您只想在代码执行了不应执行的操作时使用throw,而不是表示函数的完全有效的否定结果。

一般规则是,异常是指在执行代码期间发生异常或意外情况。查看函数的用途,在传递给向量target的向量中,没有两个整数的出现是该函数的极有可能的结果,因此该结果并非异常,因此不应视为例外。 / p>

答案 7 :(得分:5)

仅因为函数作为最后一次调用而抛出,并不意味着它正在替换return。这只是逻辑流程。

问题不应该是:

  

用throw代替return是个好习惯吗?

相反,它应该是关于:如何定义您的API和合同

如果要向函数用户保证vector永远不会为空,那么抛出异常是个好主意。

如果要保证函数不会引发异常,而是在某些条件下返回空的vector,则抛出异常是一个好主意。

在两种情况下,用户都必须知道他们必须采取什么措施才能正确使用您的功能。

答案 8 :(得分:4)

您的问题与语言无关。

返回和掷有不同的目的。

  • Return用于返回值;同时,
  • throw是引发异常;和,
    • 在真正特殊的情况下应该抛出异常。

奖励内容(摘自史蒂夫·麦康奈尔的Code Complete 2)

在遇到无法正常返回的情况时,这里提供了所有可用的替代方法:

  • 从应该返回某物计数的方法中返回-1的中性值
  • 返回汽车数字速度表上最接近的合法值,例如当汽车反向行驶时;
  • 返回与上次通话相同的值,例如每秒读取温度计上的温度读数,并且您知道在这么短的时间间隔内读数不会显着不同;
  • 返回下一个有效数据,例如当您从表中读取行并且特定行已损坏时;
  • 设置/返回在C ++库调用中很常见的错误状态/代码/对象
  • 在警报框中显示一条消息,但请注意不要过多散布内容以帮助恶意行为者;
  • 登录文件
  • 抛出异常,就像您正在思考的那样;
  • 如果这是系统的设计方式,请调用中央错误处理程序/例程
  • 关闭

此外,您无需仅选择上述选项之一。您可以进行组合,例如,登录文件向用户显示消息。

您应该采用的方法是正确与稳健。您是否希望程序绝对正确并在遇到错误情况时关闭,还是希望程序健壮并在某些时候未能遵循所需的路径继续执行?

答案 9 :(得分:3)

我认为问 返回 是否可以通过 throw < / strong>。 返回 正在做两个非常不同的事情:

  • return 用于返回函数的常规结果。
  • throw 是当结果值不存在时对错误情况做出反应的首选方法。

对错误的替代反应可能是返回特殊的错误代码值。这有一些缺点,例如:

  • 如果您始终必须牢记错误代码等,则使用功能会变得更加困难。
  • 呼叫者可能会忘记处理错误。当您忘记异常处理程序时,您将得到未处理的异常。您很有可能会检测到此错误。
  • 该错误可能太复杂,无法正确地由单个错误代码表示。异常可以是包含您需要的所有信息的对象。
  • 在定义明确的位置(异常处理程序)处理错误。与每次调用后检查返回值是否有特殊值相比,这将使您的代码更加清晰。

在您的示例中,答案取决于函数的语义:

如果空向量是可接受的返回值,那么如果未找到任何对,则应使用 return

如果期望总是必须有匹配对,那么显然找不到任何对都是错误的。在这种情况下,您应该使用 throw