简体   繁体   English

使用throw来替换C ++非void函数中的return

[英]Using throw to replace return in C++ non-void functions

In C++ functions, is it a good practice to replace return with throw ? 在C ++函数中,用throw替换return是一个好习惯吗? For example, I have the following code 例如,我有以下代码

// 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";
}

The code above compiles with my GCC 7.2. 上面的代码用我的GCC 7.2编译。

In C++ functions, is it a good practice to replace return with throw? 在C ++函数中,用throw替换return是一个好习惯吗?

Return is not something that can be replaced by a throw in general . 返回不是一般可以被投掷取代的东西。

In exceptional cases where you have nothing to return, throwing an exception can be a valid way to exit the function. 在无法返回任何内容的特殊情况下,抛出异常可能是退出函数的有效方法。

Whether it is "good practice", and what case is "exceptional" are subjective. 是否是“良好实践”,什么案例是“特殊”是主观的。 For example, for a search function such as yours, it's hardly a surprise that there might not be a solution, and I would argue that throwing would not be appropriate. 例如,对于像你这样的搜索功能,可能没有解决方案并不奇怪,我认为投掷不合适。

There are often other alternatives to throwing. 投掷通常还有其他选择。 Compare your algorithm with something like std::string::find that returns the index of the start of a substring. 将您的算法与std::string::find ,返回子字符串开头的索引。 In case where substring does not exist, it returns a "non-value" std::string::npos . 如果substring不存在,则返回“非值” std::string::npos You could do the same and decide that the index -1 is returned when a result is not found. 您可以这样做,并确定在找不到结果时返回索引-1。 There is also a generic way to add non-value representation to a type in cases where none of the existing representations can be reserved for the purpose: std::optional . 在没有为此目的保留任何现有表示的情况下,还有一种向表单添加非值表示的通用方法: std::optional

PS A vector is probably not a good choice for returning a pair of numbers. PS向量可能不是返回一对数字的好选择。 std::pair might be better, or a custom class if you have good names for the numbers. std::pair可能更好,或者如果你有数字的好名字,可以是自定义类。

The concepts of this answer are taken from the C++ Programming language by Bjarne Stroustrup. 这个答案的概念来自Bjarne Stroustrup的C ++编程语言。

SHORT ANSWER 简短的回答

Yes, exception-throwing can be used as returning value method. 是的,异常抛出可以用作返回值方法。 An example is the following for a binary tree search function: 以下是二叉树搜索功能的示例:

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;
}

However, it should be avoided because: 但是,应该避免,因为:

  • they allow you to separate error code from "ordinary code" making your program much more readable, comprehensible and manageable. 它们允许您将错误代码与“普通代码”分开,使您的程序更具可读性,易于理解和易于管理。 If you use them as return method, this does not hold anymore. 如果您将它们用作返回方法,则不再适用。
  • there might be inefficiencies because exception implementations rely on the assumption that they are used as error-handling methods. 可能效率低下,因为异常实现依赖于它们被用作错误处理方法的假设。

Apart from that, there are further limitations: 除此之外,还有其他限制:

  • exceptions must be of copy-able type 例外必须是可复制的类型
  • exceptions can handle only synchronous events 异常只能处理同步事件
  • they should be avoided in a time-critical system 在时间紧迫的系统中应该避免它们
  • they should be avoided in large old programs in which resource management is an ad hoc mess (free store is unsystematically managed using naked pointers, news and delete) rather than relying on some systematic scheme such as resource handles (strings vectors). 在大型旧程序中应避免使用它们,其中资源管理是临时混乱(使用裸指针,新闻和删除进行非系统管理的免费存储),而不是依赖于某些系统方案,如资源句柄(字符串向量)。

Longer answer 更长的答案

An exception is an object thrown to represent the occurrence of an error. 异常是抛出一个表示错误发生的对象。 It can be of any type that can be copied but it is strongly recommended to use only user-defined types specifically defined for that purpose. 它可以是任何可以复制的类型,但强烈建议仅使用专门为此目的定义的用户定义类型。 Exceptions allow the programmer to explicitly separate error-handling code from "ordinary code" making the program more readable. 例外允许程序员明确地将错误处理代码与“普通代码”分开,使程序更具可读性。

First of all, exceptions are for managing synchronous events , not asynchronous ones. 首先,例外是​​管理同步事件 ,而不是异步事件 This is one first limitation. 这是第一个限制。

One might think of the exception-handling mechanisms as simply another control structure, an alternative way of returning a value to a caller. 有人可能会将异常处理机制视为另一种控制结构,这是将值返回给调用者的另一种方法。

This has some charm but should be avoided because it is likely to cause confusion and inefficiencies. 这有一些魅力,但应该避免,因为它可能会导致混乱和效率低下。 Stroustrup suggests: Stroustrup建议:

When at all possible stick to the "exception handling is an error handling" view. 什么时候可能坚持“异常处理是一个错误处理”视图。 When this is done code is separated into two categories: ordinary code and error handling code. 完成此操作后,代码分为两类:普通代码和错误处理代码。 This makes the code more comprehensible. 这使代码更易于理解。 Furthermore, the implementations of the exception mechanisms are optimized based on the assumption that this simple model underlies the use of the exception. 此外,异常机制的实现基于这个简单模型基于异常的使用的假设而被优化。

So basically using exceptions to return value should be avoided because 所以基本上应该避免使用异常来返回值,因为

  • exception implementation is optimized assuming they are used for error-handling and not for returning values hence they might be inefficient for that; 异常实现是优化的,假设它们用于错误处理而不是返回值,因此它们可能效率低下;
  • They allow separating error code from ordinary code making the code much more readable and comprehensible. 它们允许将错误代码普通代码分离,使代码更易读和易于理解。 Anything that helps preserve a clear model of what is an error and how it is handled should be treasured . 任何有助于保持错误及其处理方式的清晰模型的东西都应该珍惜

There are programs that for practical or historical reasons cannot use exceptions (neither as error handling so even less): 有些程序由于实际或历史原因不能使用异常(既不是错误处理,也不是更少):

  • A time-critical component of an embedded system where operation must be guaranteed to complete in a specified maximum time. 嵌入式系统的时间关键组件,必须保证在指定的最大时间内完成操作。 In the absence of tools that can accurately estimate the maximum time for an exception to propagate from throw to catch alternative error handling methods must be used. 如果没有可以准确估计异常从throwcatch传播的最大时间的工具,则必须使用其他错误处理方法。
  • A large old program in which resource management is an ad hoc mess (free store is unsystematically managed using naked pointers, news and delete ) rather than relying on some systematic scheme such as resource handles ( string s vector s). 一个大的旧程序,其中资源管理是临时的混乱(使用裸指针, newsdelete非系统地管理免费存储)而不是依赖于某些系统方案,例如资源句柄( string vector s)。

In the above cases, traditional pre-exception methods are preferred. 在上述情况下,传统的异常前方法是首选。

return and throw have two different purposes and should not be considered interchangeable. returnthrow有两个不同的目的,不应该被认为是可以互换的。 Use return when you have a valid result to send back to the caller. 如果您有有效的结果,请使用return返回给调用者。 On the other hand, use throw when some exceptional behavior occurs. 另一方面,当出现一些异常行为时使用throw You can get an idea of how other programmers use throw by using functions from the standard library and taking note of when they throw exceptions. 您可以通过使用标准库中的函数并记录它们何时抛出异常来了解其他程序员如何使用throw

In addition to the other answers, there's also performance : Catching an exception incurs a run-time overhead (see this answer ) compared to an if clause. 除了其他答案之外,还有性能 :与if子句相比,捕获异常会产生运行时开销(请参阅此答案 )。

(Of course, we're talking about microseconds... Whether or not this is relevant depends on your specific use case.) (当然,我们谈论的是微秒......这是否相关取决于您的具体用例。)

A function should throw an exception when it is unable to meet its postcondition. 函数在无法满足其后置条件时应抛出异常。 (Some functions may also throw exceptions when their preconditions are not met; that's a different topic that I won't get into here.) Therefore, if your function must return a pair of integers from the vector summing to the given target, then it has no choice but to throw an exception when it can't find one. (有些函数也可能在不满足前提条件时抛出异常;这是我不会在这里讨论的另一个主题。)因此,如果你的函数必须从向量求和到给定目标返回一对整数,那么它别无选择,只能在无法找到异常时抛出异常。 But if the function's contract allows it to return a value to indicate it was unable to find such a pair, then it should not throw an exception, since it has the ability to fulfill the contract. 但是如果函数的契约允许它返回一个值来表明它无法找到这样的一对,那么它就不应该抛出异常,因为它有能力履行合同。

In your case: 在你的情况下:

  • You can make your function return an empty vector when it can't find two integers summing to the given target. 当无法找到与给定目标相加的两个整数时,可以使函数返回空向量。 Then, it never needs to throw an exception. 然后,它永远不需要抛出异常。
  • Or, you can make your function return std::optional<std::pair<int, int>> . 或者,您可以使函数返回std::optional<std::pair<int, int>> It never needs to throw an exception because it can just return an empty optional when it can't find an appropriate pair. 它永远不需要抛出异常,因为当它找不到合适的对时,它只能返回一个空的可选项。
  • If, however, you make it return std::pair<int, int> , then it should throw an exception because there is no sensible value to return when it can't find an appropriate pair. 但是,如果你让它返回std::pair<int, int> ,那么它应该抛出一个异常,因为当它找不到合适的对时没有合理的值返回。

Generally, C++ programmers prefer to write functions that don't need to throw exceptions in order to report mere "disappointments", such as search failures, that are easily anticipated and handled locally. 通常,C ++程序员喜欢编写不需要抛出异常的函数,以报告容易预期和本地处理的“失望”,例如搜索失败。 The advantages of returning values rather than throwing exceptions are discussed extensively elsewhere so I won't rehash that discussion here. 在其他地方广泛讨论了返回值而不是抛出异常的优点,因此我不会在此重复讨论。

Thus, declaring "failure" by throwing an exception is usually limited to the following cases: 因此,通过抛出异常来声明“失败”通常仅限于以下情况:

  • The function is a constructor and it's simply not able to establish its class's invariant. 该函数是一个构造函数,它根本无法建立其类的不变量。 It must throw an exception in order to ensure that the calling code doesn't see a broken object. 它必须抛出异常以确保调用代码不会看到损坏的对象。
  • An "exceptional" condition arises that should be handled at some higher level than that of the caller. 出现“特殊”情况,应该在比呼叫者更高的级别处理。 The caller is likely to not know how to recover from the condition. 呼叫者可能不知道如何从病情中恢复。 In such cases, the use of the exception mechanism frees up the caller from having to figure out how to proceed when the exceptional condition occurs. 在这种情况下,使用异常机制可以使调用者不必在异常情况发生时弄清楚如何继续。

What you have mentioned is not good programming practice. 你提到的不是很好的编程习惯。 Replacing a return statement with throw is not acceptable in production-level code, especially with automated testing platforms that generate lists of all exceptions as a way of proving or disproving certain functionality. 使用throw替换return语句在生产级代码中是不可接受的,特别是对于生成所有异常列表的自动化测试平台,作为证明或反驳某些功能的方式。 You have to take into consideration the needs of Software Testers when designing your code. 在设计代码时,您必须考虑软件测试人员的需求。 throw is simply not interchangeable with return . throwreturn无法互换。 throw is used to signal that a program error has occurred by some unexpected phenomena. throw用于表示某些意外现象发生了程序错误 return is used to signal method completion. return用于表示方法完成。 It is common to use return to transmit error codes, but return values do not cause the program to be interrupted in the same way as throw does. 通常使用return来传输错误代码,但返回值不会导致程序以与throw相同的方式被中断。 Additionally, throw has the power to terminate a program if it is not handled correctly. 此外,如果没有正确处理, throw可以终止程序。

Essentially, it is good practice to use throw when a significant error is detected within the method, but there should always be a clean return if such an error is not detected. 从本质上讲,当在方法中检测到重大错误时使用throw是一个好习惯,但如果没有检测到这样的错误,应该总是有一个干净的返回。 Can you make that substitution? 你能做出替代吗? Maybe, and maybe it will work logically...but it's not good practice and certainly not a popular implementation. 也许,也许它会在逻辑上起作用......但这不是一个好的做法,当然也不是一个流行的实现。

No, throw is not a good semantic replacement for return . 不, throw不是return的良好语义替代品。 You only want to use throw when your code has done something that it should not be doing, not to signify the perfectly valid negative result of a function. 你只想在你的代码完成它不应该做的事情时使用throw ,而不是表示函数的完全有效的否定结果。

As a general rule, exceptions are meant to signify when something abnormal or unexpected has happened during the execution of your code. 作为一般规则,异常意味着表示在执行代码期间发生异常或意外事件的时间。 Looking at the purpose of your function, the occurrence of no two integers in the passed vector summing to target is a very possible result of the function, so that result is not abnormal and thus should not be treated as exceptional. 看一下你的函数的目的,在传递的向量求和到target中没有两个整数的出现是函数的一个非常可能的结果,因此结果不是异常的,因此不应该被视为异常。

Just because the function is throwing as the last call, doesn't mean it is replacing return. 仅仅因为函数作为最后一次调用而抛出,并不意味着它正在替换return。 It is just the flow of logic. 这只是逻辑的流动。

The question shouldn't be : 问题不应该是:

is it a good practice to replace return with throw? 用throw代替return是一个好习惯吗?

Instead it should be about : how to define your API and contracts . 相反,它应该是: 如何定义您的API和合同

If You want to guarantee to the users of the function that vector is never empty, then throwing an exception is a good idea. 如果你想向函数的用户保证vector永远不会为空,那么抛出异常是一个好主意。

If you want to guarantee that your function doesn't throw exceptions instead returns an empty vector on certain conditions, then throwing is a bad idea. 如果你想保证你的函数不会抛出异常而是在某些条件下返回一个空vector ,那么抛出是一个坏主意。

In both cases, the user has to know, what actions they have to take to correctly use your function. 在这两种情况下,用户必须知道他们必须采取什么行动才能正确使用您的功能。

Your question is language agnostic. 你的问题与语言无关。

Return and Throw have different purposes. 返回和投掷有不同的目的。

  • Return is for returning a value; 返回是为了返回一个值; while, 而,
  • Throw is for throwing an exception; 扔是为了抛出异常; and, 和,
    • An exception should be thrown in truly exceptional conditions. 在真正特殊情况下应该抛出异常。

Bonus Content (from Steve McConnell's Code Complete 2) 奖金内容 (来自Steve McConnell的代码完成2)

Here are all the alternatives available to you when you encounter a situation where you cannot return normally: 当您遇到无法正常返回的情况时,以下是您可以使用的所有备选方案:

  • Return a neutral value like -1 from a method that's supposed to return count of something; 从一个应该返回一些东西的方法中返回一个像-1这样的中性值 ;
  • Return the closest legal value like 0 on the digital speedometer of a car when it is going in reverse; 当汽车倒车时,在汽车的数字车速表上返回最接近的合法值,如0;
  • Return same value as previous call like a temperature reading on a thermometer when you are reading every second and you know that the readings do not differ drastically in such a short interval; 当你每秒钟读取时, 返回与之前调用相同的值,如温度计上的温度读数,并且您知道读数在如此短的时间间隔内没有显着差异;
  • Return the next valid piece of data like when you are reading rows from a table and a specific row is corrupt; 返回下一个有效数据,例如从表中读取行并且特定行已损坏时;
  • Set/Return an error status/code/object found pretty commonly in C++ library calls; 设置/返回在C ++库调用中常见的错误状态/代码/对象 ;
  • Display a message in an alert box but be careful to not give out too much that can assist a malicious actor; 在警告框中显示消息,但要注意不要过多地提供可以帮助恶意行为者的消息;
  • Log to a file ; 登录到文件 ;
  • Throw an exception like you are pondering upon; 抛出一个像你正在思考的异常 ;
  • Call a central error handler/routine if that is how your system is designed; 如果您的系统设计如此,请调用中央错误处理程序/例程 ;
  • Shutdown . 关机

Further, you do not need to pick only one of the above options. 此外,您不需要只选择上述选项之一。 You can do a combination like, for example, logging to file and displaying a message to the user. 您可以执行组合操作,例如,记录到文件并向用户显示消息。

What approach you should take is a question of Correctness vs Robustness . 你应该采取什么方法是正确性与健壮性的问题。 Do you want your program to be absolutely correct and shutdown when an erroneous situation is encountered or do you want your program to be robust and continue with execution when at some point it fails to follow the desired path? 您是否希望您的程序绝对正确并在遇到错误情况时关闭,或者您希望程序是否健壮并在某些时候继续执行它无法遵循所需的路径?

I think it is the wrong question asking if return can be replaced by throw . 我认为这是一个错误的问题,询问是否可以用throw 代替 return return and throw are doing two very different things: returnthrow正在做两件截然不同的事情:

  • return is used to return regular results of a functions. return用于返回函数的常规结果。
  • throw is the preferred method to react on error conditions when a result value does not exist. 当结果值不存在时, throw是对错误条件作出反应的首选方法。

The alternative reaction on errors could be to return special error code values. 对错误的替代反应可能是返回特殊错误代码值。 This has some disadvantages, eg: 这有一些缺点,例如:

  • Using a function gets more difficult if you always have to keep error codes etc. in mind. 如果您始终必须记住错误代码等,则使用函数会变得更加困难。
  • The caller might forget to handle the error. 调用者可能忘记处理错误。 When you forget an exception handler, you will get unhandled exception. 当您忘记异常处理程序时,您将获得未处理的异常。 It is very likely that you will detect this bug. 您很可能会发现此错误。
  • The error might be too complex to be expressed by a single error code properly. 错误可能过于复杂,无法正确表达单个错误代码。 An exception can be an object containing all the information you need. 异常可以是包含所需信息的对象。
  • The errors are handled at a well-defined position (the exception handler). 错误在明确定义的位置处理(异常处理程序)。 This will make your code much clearer than checking the return value for special values after each call. 这将使您的代码比在每次调用后检查特殊值的返回值更清晰。

In your example the answer depends on the semantics of the function: 在您的示例中,答案取决于函数的语义:

If an empty vector is an acceptable return value, then you should use return if no pair is found. 如果空向量是可接受的返回值,那么如果没有找到对,则应该使用return

If it is expected that there MUST be matching pairs always, then finding no pair obviously is an error. 如果预计总是必须匹配对,那么找不到对显然是一个错误。 You should use throw in this case. 在这种情况下你应该使用throw

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 C ++中void和非void函数之间的区别 - Difference between void and non-void functions in C++ C ++重载返回void与非void - C++ overload return void vs. non-void C ++:是否可以在不返回的情况下终止非无效函数? - C++: Is It Possible to End a Non-Void Function Without a Return? C ++模板错误,不返回,函数返回非空 - C++ template error, No return, in function returning non-void 发生异常时返回什么非void函数,而不使用异常? C ++ - What to return for a non-void function when exception occurs, without using exception? C++ 使用 C++ 时返回非 void 的函数中没有 return 语句 - no return statement in function returning non-void while using C++ 省略抛出的非空 function 模板的返回语句是否有效 - Is it valid to omit the return statement of a non-void function template that throw 可以同时执行非空函数的多线程吗? (C ++) - Is it possible to multithread to execute non-void functions at the same time? (C++) gcc选项:在没有return语句的情况下对非void函数发出警告 - gcc options: warning on non-void functions without a return statement 非void函数的C ++崩溃没有return语句 - C++ Crash on a non-void function doesn't have return statement
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM