在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一起编译。
答案 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
的最大时间的工具的情况下,必须使用其他错误处理方法。news
和delete
非系统地管理免费存储),而不是依靠诸如资源句柄( string
s vector
s)。在上述情况下,首选传统的前置例外方法。
答案 2 :(得分:19)
return
和throw
有两个不同的用途,不应视为可互换。当您有有效的结果发送回呼叫者时,请使用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)
您的问题与语言无关。
返回和掷有不同的目的。
奖励内容(摘自史蒂夫·麦康奈尔的Code Complete 2)
在遇到无法正常返回的情况时,这里提供了所有可用的替代方法:
此外,您无需仅选择上述选项之一。您可以进行组合,例如,登录文件和向用户显示消息。
您应该采用的方法是正确与稳健。您是否希望程序绝对正确并在遇到错误情况时关闭,还是希望程序健壮并在某些时候未能遵循所需的路径继续执行?
答案 9 :(得分:3)
我认为问 返回 是否可以通过 throw < / strong>。 返回 和 扔 正在做两个非常不同的事情:
对错误的替代反应可能是返回特殊的错误代码值。这有一些缺点,例如:
在您的示例中,答案取决于函数的语义:
如果空向量是可接受的返回值,那么如果未找到任何对,则应使用 return 。
如果期望总是必须有匹配对,那么显然找不到任何对都是错误的。在这种情况下,您应该使用 throw 。