use*_*490 47 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";
}
Run Code Online (Sandbox Code Playgroud)
上面的代码用我的GCC 7.2编译.
eer*_*ika 40
在C++函数中,用throw替换return是一个好习惯吗?
返回不是一般可以被投掷取代的东西.
在无法返回任何内容的特殊情况下,抛出异常可能是退出函数的有效方法.
是否是"良好实践",什么案例是"特殊"是主观的.例如,对于像你这样的搜索功能,可能没有解决方案并不奇怪,我认为投掷不合适.
投掷通常还有其他选择.将您的算法与类似的东西进行比较std::string::find,返回子字符串开头的索引.如果子串不存在,则返回"非值" std::string::npos.您可以这样做,并确定在找不到结果时返回索引-1.在没有为此目的保留任何现有表示的情况下,还有一种向表单添加非值表示的通用方法:std::optional.
PS向量可能不是返回一对数字的好选择.std::pair可能会更好,或者如果你有数字的好名字,可以是自定义类.
Fra*_*Boi 30
这个答案的概念来自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;
}
Run Code Online (Sandbox Code Playgroud)
但是,应该避免,因为:
除此之外,还有其他限制:
异常是抛出一个表示错误发生的对象.它可以是任何可以复制的类型,但强烈建议仅使用专门为此目的定义的用户定义类型.例外允许程序员明确地将错误处理代码与"普通代码"分开,使程序更具可读性.
首先,例外是管理同步事件,而不是异步事件.这是第一个限制.
有人可能会将异常处理机制视为另一种控制结构,这是将值返回给调用者的另一种方法.
这有一些魅力,但应该避免,因为它可能会导致混乱和效率低下.Stroustrup建议:
什么时候可能坚持"异常处理是一个错误处理"视图.完成此操作后,代码分为两类:普通代码和错误处理代码.这使代码更易于理解.此外,异常机制的实现基于这个简单模型基于异常的使用的假设而被优化.
所以基本上应该避免使用异常来返回值,因为
有些程序由于实际或历史原因不能使用异常(既不是错误处理,也不是更少):
throw,以catch替代的错误处理方法必须使用.news并且delete)而不是依赖于某些系统方案,例如资源句柄(strings vectors).在上述情况下,传统的异常前方法是首选.
Cod*_*ice 19
return并且throw有两个不同的目的,不应该被认为是可以互换的.使用return时,你有一个有效的结果发回给调用者.另一方面,throw在发生某些异常行为时使用.您可以throw通过使用标准库中的函数并注意它们何时抛出异常来了解其他程序员的使用方式.
Ped*_* LM 15
除了其他答案之外,还有性能:与子句相比,捕获异常会产生运行时开销(请参阅此答案)if.
(当然,我们谈论的是微秒......这是否相关取决于您的具体用例.)
Bri*_*ian 10
函数在无法满足其后置条件时应抛出异常.(有些函数也可能在不满足前提条件时抛出异常;这是我不会在这里讨论的另一个主题.)因此,如果你的函数必须从向量求和到给定目标返回一对整数,那么它别无选择,只能在无法找到异常时抛出异常.但是如果函数的契约允许它返回一个值来表明它无法找到这样的一对,那么它就不应该抛出异常,因为它有能力履行合同.
在你的情况下:
std::optional<std::pair<int, int>>.它永远不需要抛出异常,因为当它找不到合适的对时,它只能返回一个空的可选项.std::pair<int, int>,那么它应该抛出一个异常,因为当它找不到合适的对时没有合理的值返回.通常,C++程序员喜欢编写不需要抛出异常的函数,以报告容易预期和本地处理的"失望",例如搜索失败.在其他地方广泛讨论了返回值而不是抛出异常的优点,因此我不会在此重复讨论.
因此,通过抛出异常来声明"失败"通常仅限于以下情况:
你提到的不是很好的编程习惯.在生产级代码中替换return语句throw是不可接受的,特别是对于生成所有异常列表的自动化测试平台,作为证明或反驳某些功能的方式.在设计代码时,您必须考虑软件测试人员的需求.throw根本不可以互换return.throw用于表示某些意外现象发生程序错误.return用于表示方法完成.通常用于return传输错误代码,但返回值不会导致程序以与中断相同的方式中断throw.此外,throw如果程序处理不当,有权终止程序.
从本质上讲,最好throw在方法中检测到重大错误时使用,但如果未检测到此类错误,则应始终保持清洁返回.你能做出替代吗?也许,也许它会在逻辑上起作用......但这不是一个好的做法,当然也不是一个流行的实现.
不,throw不是一个很好的语义替代品return.您只希望throw在代码执行了不应该执行的操作时使用,而不是表示函数的完全有效的否定结果.
作为一般规则,异常旨在表示在执行代码期间发生异常或意外事件的时间.看一下函数的用途,在传递的向量求和中没有两个整数的出现是函数的target一个非常可能的结果,因此结果不是异常的,因此不应该被视为异常.
您的问题与语言无关。
Return和Throw具有不同的目的。
奖励内容(摘自史蒂夫·麦康奈尔的《 Code Complete 2》)
当遇到无法正常返回的情况时,以下是所有可用的替代方法:
此外,您无需仅选择以上选项之一。您可以进行组合,例如,记录到文件并向用户显示消息。
您应该采用哪种方法来解决“ 正确性与鲁棒性”问题。您是否希望程序绝对正确并在遇到错误情况时关闭,还是希望程序健壮并在某些时候无法遵循所需的路径继续执行?
仅仅因为该函数将作为最后一个调用而抛出,并不意味着它正在代替return。这只是逻辑流程。
问题不应该是:
用throw代替return是一个好习惯吗?
相反,它应该是关于:如何定义您的API和合同。
如果要向vector永远不为空的函数的用户保证,则抛出异常是一个好主意。
如果要保证函数不会抛出异常,而是vector在某些条件下返回空值,那么抛出则是个坏主意。
在这两种情况下,用户都必须知道他们必须采取什么措施才能正确使用您的功能。