我看到一个使用throw
返回内容的函数。
我在问这个问题之前已经做了一些研究,关于throw
我并没有找到很多。
如果有人可以解释我何时使用throw
返回某些内容以及何时使用正常的return
语句,那就太棒了。
答案 0 :(得分:1)
使用throw
返回结果在深度嵌套的递归中很有用。而不是退出每个呼叫级别,您可以直接回到顶级呼叫以实现此目标。我认为在大多数其他情况下,它的效率太低(因为异常是针对故障处理进行了优化的)而且非常规甚至无法考虑。
答案 1 :(得分:0)
throw不用于返回任何值,它用于抛出异常,即如果您认为程序中的某个条件会导致运行时错误或故障,那么您抛出一个有助于避免和处理的异常有这样的运行时错误。返回用于从函数和值返回到调用函数。
答案 2 :(得分:0)
投掷可用于处理异常。看看这段代码:
#include <iostream>
#include <stdexcept>
int i = 0;
int main() {
if(i == 0) {
throw std::runtime_error("Error, I is equal to 0");
}
std::cout << "I is not equal to zero, program finished.";
}
如果发生错误,在此实例中“i”等于零,则整个程序停止并收到此错误消息:
terminate called after throwing an instance of 'std::runtime_error'
what(): Error, I is equal to 0
但是如果删除了throw语句,则该异常将不起作用,请继续尝试。
另一方面,使用返回,以便在执行函数后,您可以知道该函数的作用。看看这段代码:
#include <iostream>
#include <stdexcept>
int i = 0;
int errorOccured() {
return true;
}
int main() {
bool x = errorOccured();
if(x == true) {
std::cout << "Major error occured";
}
else if(x == false) {
std::cout << "No error occured";
}
}
函数errorOccured()在这种情况下返回true,并且你能够将返回的值存储在X中。“return”不仅仅局限于错误处理,你可以做返回的各种事情。您可以使函数执行复杂的计算,然后返回结果。所以你有它,有两种不同的方法来查看执行时函数的运行情况。
你可以找到关于投掷here和关于返回here的更多信息。一个人也提出了“捕获”,你可以阅读here并进行一些有趣的阅读。
答案 3 :(得分:0)
return
是最简单的,在许多情况下也是返回计算值,对类成员的引用,状态信息(例如枚举值),琐碎成功/失败信息的最有效方式,和,和。因此,这是评估函数值的最常用和最常用的方法。
throw
(就像Alf已经回答的那样)处理exception
- 正如名称已经非常明确地指出:)正常控制流的例外: &#34;发生了一些非常重要的事情(通常是坏事),这需要立即处理,我不能继续我通常的处理流程并且必须立即抛出异常。&#34;。原因有时可能是程序中的灾难性和不可恢复的状态,但绝不是总是如此。例如,一个套接字失去连接是很常见的而不是灾难 - 但可能是抛出异常的原因,因为套接字代码可能无法继续计算。
异常通常比平常的返回值更难以处理 - 并且整合到您的程序设计中 - 并且它们 - 正如其他人已经说过的那样 - 效率低于简单的返回值,但它们有很多好处。
可以从调用函数堆栈中的任何位置捕获它们 让我们假设您使用游戏引擎(例如Ogre)编写游戏,并且此引擎使用直接X界面。现在,DirectX界面中出现了一些深层次的东西,这阻碍了引擎正常工作。 这个函数的错误处理(可能是调用堆栈中的8-10个调用),如果不能正常返回值,将几乎不可能 - 对于引擎程序员和游戏都是如此程序员。所以在这种情况下,没有例外,选择的方法将是一个非标准化的错误处理程序 - 非常类似于异常,但不具备异常的强大可能性。这是一个关于如何处理这个错误的一个实际例子,例外(请忽略函数的真正目的,它只是为了说明原理:
try
{
mHOQList[mCurrentFrame]->endOcclusionQuery();
} catch( Ogre::Exception& e )
{
if( e.getNumber() == Exception::ERR_RENDERINGAPI_ERROR
&& stdEx::string(e.getDescription()).beginsWith( "End occlusion called" ))
{
// a device lost occurred during our occlusion query. Simply ignore it.
return true;
}
else
throw;
}
我们正在这里进行遮挡查询,我们知道当一个&#34;设备丢失&#34;事件发生在它的操作过程中。所以我们将它放在try / catch子句中。当endOcclusionQuery()
中的所有内容都运行良好时,catch()
永远不会被调用,一切都很好。
如果抛出异常,我们首先检查是否可以处理它。我们检查异常的编号及其描述。如果这些信息具有特定值,我们知道这是一个良性错误,我们可以放心地忽略它并继续下一帧。如果我们不知道如何处理它,我们只需throw;
,这使得exceplion的处理在调用层次结构中低于catch()
,这将我带到下一点:< / p>
可以有选择地评估它们。
上面的示例将捕获并处理类型Ogre::Exception
的异常,但没有其他内容。未捕获std :: exception或其他异常类型。让我们举个例子说endOcclusionQuery()
在我们自己的代码中调用一个回调,然后它也会进入异常状态并抛出。我们会让这个异常通过,并将其留给调用层次结构的较低(或更高)级别来处理它。
他们可以重新投入。
在该示例中,我们使用throw;
重新抛出并将处理传递给调用层次结构中的较低级别。
它们可以存储,甚至可以在单独的线程中重新抛出 想象一个包含数百个工作线程的库,以及一个协调这些线程的管理器线程。由于异常绑定到单个线程,因此管理器线程永远不会从工作线程中捕获异常。但是工作线程可以捕获它们自己的异常并在可能的情况下处理它们或者存储它们,将它们传递给管理器线程,在那里它可以被重新抛出并由管理器线程处理。
可以在构造函数中抛出它们 构造函数没有返回值,因此无法使用返回值检查其成功。国家成员是可能的,但很尴尬 - 他们往往被忽视。因此,在构造函数中处理错误的优选方法是抛出(当然是记录的行为)。另请参阅Throwing exceptions from constructors
他们是标准化的
当谈到这一点时,上面的例子不是最好的。早在C ++ 11之前,Ogre就是一个非常古老的引擎,因此异常类是专有的。你可以抛出任何东西 - 从char
到class LiverSausage
。但是今天不应该再这样做了 - std::exception
是选择的类别。它包含简单异常所需的所有内容,并且可以继承更复杂的异常。它通常在STL中使用和继承,并且有助手类和函数,如std::exception_ptr
,std::current_exception()
等。
它们可以用作对不可恢复的程序错误的安全救助。最后,不幸的是,这样的丑陋事件可能发生在最好的程序中。你可以在你的程序中的任何地方抛出这个致命的异常,你可以在一个地方catch
这个异常,在那里你可以记录错误,评估它的来源,甚至可以写一个转储 - 所以你至少一个可能发生的事情的线索,这至少比简单地崩溃还要糟糕;)
答案 4 :(得分:0)
虽然这可能听起来很糟糕,但我实际上让表现成为指导这一决定的一个重要因素。大多数现代优化器实现了所谓的零成本异常处理,最终转换为“无分支的正常执行路径,但昂贵的异常路径”。
它使throwing
相当昂贵,以换取正常的执行路径非常便宜。我没有精确的成本数字,但如果您使用try/catch
块来测试数据结构中是否存在密钥,则可能相对非常昂贵,例如。
我发现有用的另一个指导力是在程序员控制之外的外部异常的想法。例如,客户端无法连接到服务器,该服务器应该在重复尝试后出现,遇到损坏的文件,无法分配内存,这类事情。
我曾与一位同事讨论过用户是否在进度条上干扰中止按钮是否属于特殊情况。我之所以认为这样做是因为操作通常应该成功,并且用户中止是开发人员无法控制的真正特殊情况。最重要的是,它确实简化了代码,试图在整个调用堆栈中传播中止状态。
在这些人们可能不同意什么是特殊控制流程并且不是特殊控制流程的情况下,我将性能视为决定性因素。抛弃一个昂贵的操作并不是一个很大的性能开销,因为它不像我们在关键循环中投入了一百万次。我们只是浪费一次中止非常昂贵的操作,并且投掷的开销变得非常微不足道。所以当我谈到绩效是一个决定性因素时,这就是我的意思。