简体   繁体   English

抛出还是不抛出异常?

[英]To throw or not to throw exceptions?

I was talking to a friend of mine that through my new code I didn't treat exceptions, just because I didn't know how to do it in C++. 我在和我的一个朋友聊天,说通过我的新代码,我没有对待异常,只是因为我不知道如何在C ++中实现。 His answer surprised me: "why in the hell would you want to throw excetions?". 他的回答使我感到惊讶:“为什么要在地狱里丢东西?”。 I asked him why, but he didn't have a satisfying answer, so I googled it. 我问他为什么,但是他没有令人满意的答案,所以我用谷歌搜索。 One of the first pages I found was a blog entry, where the guy who posted wasn't totally against exceptions, but a religious war started in the replies: http://weblogs.asp.net/alex_papadimoulis/archive/2005/03/29/396141.aspx 我发现的第一页内容是一个博客条目,发布该帖子的人并不完全反对例外,但是在答复中引发了宗教战争: http : //weblogs.asp.net/alex_papadimoulis/archive/2005/03 /29/396141.aspx

Now I begin to wonder: is it that bad to throw an exception? 现在我开始怀疑:抛出异常是否很糟糕? For a student like I am, is it wrong to learn programming using exceptions as a normal thing? 对于像我这样的学生来说,使用异常作为常识来学习编程是错误的吗? (When I throw exceptions, I catch them in another level of the code to treat them, most of the times). (大多数时候,当我抛出异常时,我会将它们捕获在另一级代码中进行处理)。 I have a code example, and I want to know what should I do: 我有一个代码示例,我想知道应该怎么做:

int x;
cout << "Type an integer: ";
cin >> x;

Anything that is typed there that is not an integer will trigger an exception already, right? 在那里输入的任何不是整数的东西都会触发异常,对吗? This exception should be treated there. 该异常应在此处处理。 But when I have a possible exception inside a class that is being used elsewhere in the program, should I make this method throw an exception so I can treat it wherever I call it or should I make it return a standard value when it has any problems? 但是,当我在程序中其他地方使用的类中有可能的异常时,我应该使此方法引发异常,以便可以在我调用它的任何地方对其进行处理,或者在有任何问题时使它返回标准值?

The exception is always good, always bad, or something to be used in "exceptional" situations? 例外总是好,总是坏,还是在“例外”情况下要使用的东西? Why? 为什么?

The C++ iostreams classes do not, by default, use exception handling. 默认情况下,C ++ iostreams类不使用异常处理。 Generally one should use exceptions for situations where an error can occur, but such errors are "unusual" and "infrequent" (such as a disk failing, the network being down, etc.). 通常,对于可能发生错误的情况,应该使用例外,但此类错误是“不常见的”和“不频繁的”(例如磁盘故障,网络故障等)。 For error conditions that you do expect (such as the user providing invalid input), you should probably not use exception handling, unless the logic that needs to deal with this situation is far removed from the logic in which the situation is detected, in which case using an exception is the way to go. 对于您确实期望的错误条件(例如,用户提供了无效的输入),除非应该将处理该情况的逻辑与检测到该情况的逻辑相去甚远,否则可能不应使用异常处理。使用异常的情况是解决方法。 There is nothing wrong with using exceptions, and using exceptions where they are merited is definitely good... just avoid using them where if...else works just fine. 使用异常并没有什么错,并且应该在异常值得使用的地方使用异常绝对是件好事……只要在...不使用的情况下避免使用它们,否则其他工作就可以了。

As to the why: 至于原因:

  1. Exceptions often provide a simple, elegant way of handling unexpected errors, but: 异常通常提供处理意外错误的简单,优雅的方法,但是:
  2. Exception propagation, depending on the compiler and platform, may be slower than ordinary control flow, so if you can handle the situation locally, then that will be at least as fast... and you especially don't want to slow down the common, expected cases (such as invalid input). 根据编译器和平台的不同,异常传播的速度可能比普通控制流慢,因此,如果您可以在本地处理这种情况,那么至少会如此之快……而且您尤其不想降低通用速度,预期的情况(例如无效的输入)。 Also: 也:
  3. Exceptions require more type information to be present and also require code that is unrelated to throwing or catching exceptions to be "exception-safe" which is why some object to exceptions. 异常需要提供更多的类型信息,并且还要求与引发或捕获异常无关的代码是“异常安全的”,这就是为什么某些对象反对异常的原因。

Question 1: 问题1:

There is no reason not to use exceptions. 没有理由不使用异常。
Whether to use exceptions or other techniques depends on the situation. 是否使用异常或其他技术取决于情况。

Exceptions are good to use when you can't fix the error locally. 当您无法在本地修复错误时,可以使用异常。
(This should be an exceptional situation :-) (这应该是一种特殊情况:-)

Error codes are good when you can fix the error locally. 当您可以在本地修复错误时,错误代码非常有用。

Error codes are bad if you have a library that exposes an interface that returns an error code that needs to be manually checked. 如果您有一个库公开一个返回需要手动检查的错误代码的接口的库,那么错误代码就很糟糕。 In the C world forgetting to check the error code was a significant source of problems. 在C语言世界中,忘记检查错误代码是造成问题的重要原因。

Exceptions are good when you want to force the user of your interface to check for an error. 当您要强制界面的用户检查错误时,异常是好的。 If the user does not explicitly check and compensate; 如果用户没有明确检查和补偿; then the exception will propagate until either it is handled or the application exits. 那么该异常将一直传播,直到被处理或应用程序退出为止。

Problems that come with exceptions: 带有异常的问题:
If your code is a combination of C and C++. 如果您的代码是C和C ++的组合。 Functions with C ABI do not contain enough information for exceptions to propagate. 使用C ABI的函数所包含的信息不足,无法传播异常。 Thus throwing an exception across a C function can (depending on the compiler and other factors) cause problems. 因此,跨C函数引发异常可能会导致问题(取决于编译器和其他因素)。

If you register a callback functions with a C library that uses C++ code. 如果您在使用C ++代码的C库中注册了回调函数。 Then you must make sure that all exceptions are caught in the callback function and not allowed to propagate back to the C-library. 然后,您必须确保所有异常都在回调函数中捕获并且不允许传播回C库。

But this is true when combining any two languages. 但这在组合任何两种语言时都是正确的。 You can only use the most primitive features across language boundaries (and even that can take work if the languages do not align well). 您只能跨语言边界使用最原始的功能(即使语言不能很好地对齐,也可以使用)。 Therefore trying to use any advanced features is usually not supported. 因此,通常不支持尝试使用任何高级功能。

Question 2: 问题2:

C++ streams by default do not use exceptions. 默认情况下,C ++流不使用异常。
This is because usually you want to handle the problem immediately. 这是因为通常您想立即处理该问题。

In your example you use input as an example. 在您的示例中,以输入为例。 In this situation you want to check the error and re-request the input. 在这种情况下,您要检查错误并重新请求输入。

int x;
cout << "Type an integer: "; 
cin >> x;
while(!cin)
{
    cin.clear();
    std::cout << "Failed: What do you think an integer is? Try again: ";
    cin >> x;
}

Note: 注意:

Personally I don't like the phrase 'Use exceptions in Exceptional circumstances". To me that is just to vague. vector::at() throws an exception. For me accessing off the end of an array is going to be exceptional (but for Joe student I doubt it will be exceptional (it will happen every second class as the lecturer throws a new curve ball at him/her)). 我个人不喜欢“在异常情况下使用异常”这一短语。对我来说,这只是含糊其词。vector :: at()抛出异常。对我来说,访问数组末尾将是一种例外(但对于Joe学生,我怀疑这会是例外的情况(第二堂课会发生,因为讲师向他/她扔了一个新的曲线球)。

Thus I prefer the term "When the problem can not be fixed locally". 因此,我更喜欢术语“当问题不能在本地解决时”。 A problem that can not be fixed locally is when something big has gone wrong (resource depletion (memory full throw bad_alloc()), coding error (access beyond array bounds (throw range_check()))) or any number of things that can not be fixed right there. 无法在本地解决的问题是,当发生大错误时(资源耗尽(内存已满抛出bad_alloc()),编码错误(超出数组范围的访问(抛出range_check())))或许多其他无法解决的问题固定在那里。

There is actually a legitimate reason behind why you shouldn't use exceptions. 实际上,为什么不应该使用异常是有正当理由的。

The problem is when you throw exception across boundaries (eg from one DLL and try...catch it in another DLL) sometimes things can go very wrong. 问题是当您跨边界抛出异常时(例如,从一个DLL尝试并在另一个DLL中捕获它),有时情况可能会非常错误。 I got a slap on the wrist too when I used it in one of my projects. 当我在其中一个项目中使用手腕时,我也被打了一巴掌。

Just google it you'll find all sorts of post on this topic, at least it was a problem few years ago when I was still a fulltime C++ developer :) 只要在Google上搜索它,您就会发现有关该主题的各种文章,至少在几年前我还是一名全职C ++开发人员时,这是一个问题:)

I tend to follow this convention for internal parts of my applications code: 对于我的应用程序代码的内部部分,我倾向于遵循以下约定:

  • Do not throw exceptions of your own. 不要抛出自己的异常。
  • Catch any exception that could be expected from your calls to other code inmediately. 立即捕获对您调用其他代码可能期望的任何异常。 Treat it accordingly, for example returning an error code, or "false" with the meaning of no success. 相应地对其进行处理,例如返回错误代码,或者表示“不成功”的“ false”。

However when designing components , I do use component-scope exceptions, like for example, if connecting to a required database (one which is set up by the installer) fails. 但是,在设计组件时 ,我确实使用了组件范围的异常,例如,如果连接到所需的数据库(由安装程序设置的一个数据库)失败的话。 Or when you try to call any method violating its preconditions. 或者,当您尝试调用任何违反其前提条件的方法时。 In general, preconditions are expressed with assertions; 通常,先决条件用断言表示; however, in the public layer of your component, they should be checked and an exception thrown if they are violated, otherwise the caller has a hard job finding what they are doing wrong. 但是,在组件的公共层中,应该检查它们,如果违反它们,则抛出异常,否则,调用者将很难找到他们在做什么错。 (An alternative is of course to use a debug version, in which failed assertions will scream at you; but this is not always practical - distributing two binaries, keeping them up to date, etc.) (当然,另一种选择是使用调试版本,在该版本中,失败的断言会向您大喊;但这并不总是可行的-分发两个二进制文件,使它们保持最新状态,等等。)

In my opinion, the only general rule is to be consistent and write correct code - choose some way to handle the erroneous/exceptional situations, and follow it consistently. 我认为,唯一的通用规则是保持一致并编写正确的代码-选择某种方式来处理错误/异常情况,并始终如一地遵循。

I think exception handling/throwing can be boiled down to the following: 我认为异常处理/抛出可以归结为以下几点:

  • Use RAII (or using in C#, or with in Python) so your code is exception safe. 使用RAII(或者using在C#中,或with在Python),所以你的代码是异常安全。
  • Catch exceptions if there's a chance to recover from the error, else let them bubble up. 如果有机会从错误中恢复,则捕获异常,否则让异常冒出来。
  • Throw exception only if you have something useful to add. 仅当您要添加有用的东西时,才引发异常。

Consider a hypothetical example where you're using a serial port communication library to communicate with an external sensor of sorts. 考虑一个假设的示例,其中您使用串行端口通信库与各种外部传感器进行通信。 Furthermore: 此外:

  • The serial port communication library reports errors by throwing exceptions. 串行端口通信库通过引发异常来报告错误。
  • The sensor itself can communicate certain error messages through status codes in cases when it's no longer able to acquire data, when it's functioning outside its max/min operating conditions, etc. 当传感器不再能够获取数据,在最大/最小工作条件之外运行时,传感器本身可以通过状态代码传达某些错误消息。
  • You're using RAII (Resource Acquisition Is Initialization), so if the serial port library throws an exception you wouldn't have to worry about releasing any acquired resources. 您正在使用RAII(资源获取即初始化),因此,如果串行端口库引发异常,则无需担心释放任何获取的资源。

If the serial port library thrown an exception, you can: 如果串行端口库引发异常,则可以:

  1. Catch the exception and try to recover from it somehow, eg by reinitializing the library. 捕获异常并尝试以某种方式从异常中恢复,例如,通过重新初始化库。
  2. Catch the exception, wrap it in your own, and then throw the new exception up the call stack. 捕获异常,将其包装为您自己的异常,然后将新异常抛出到调用堆栈中。

Option #1 is unlikely in most cases. 在大多数情况下,选项#1不太可能。 And option #2 would probably not add any valuable information to what's already in the original exception. 选项#2可能不会在原始异常中添加任何有价值的信息。

So usually it's best to allow original exception to bubble up (this is again assuming the RAII is in place and no clean up is needed). 因此,通常最好是让原始异常冒出来(这再次是假设RAII到位并且不需要清理)。

On the other hand, if the sensor itself reports an error code, then it makes a lot of sense to throw your own exception. 另一方面,如果传感器本身报告错误代码,则抛出自己的异常非常有意义。

This one is also a very interesting and controversial reading on the argument: 关于这一论点,这也是一个非常有趣和有争议的读物:

http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx

Throw an exception if, and only if, the alternative is failure to meet a post condition or to maintain an invariant. 当且仅当备选方案是不满足发布条件或保持不变时,才引发异常。

That advice replaces an unclear subjective decision (is it a good idea ) with a technical, precise question based on design decisions (invariant and post conditions) you should already have made. 该建议将基于您应该已经做出的设计决策(不变和后期条件)替换为一个技术性的,精确的问题,从而代替了一个不清楚的主观决定(这是一个好主意 )。

If you just blindly go and say I won't use exceptions it means you're not going to use most of the STL. 如果您只是盲目地说我不会使用异常,则意味着您不会使用大多数STL。 That means you need to write your own classes for strings/vector etc etc ... 这意味着您需要为字符串/向量等编写自己的类...

Plus exceptions are a much cleaner way of doing error handling (IMHO) and if you already do RAII then adding exceptions is not much harder. Plus异常是执行错误处理(IMHO)的一种更简洁的方法,如果您已经进行过RAII,那么添加异常并不难。

Read everything in Section 14 upto section 14.2 of Bjarne Stroustrup: "C++ programming Language Third Edition" 阅读Bjarne Stroustrup的第14节到14.2节中的所有内容: “ C ++编程语言第三版”

It is the most convincing answer I have come across. 这是我遇到的最令人信服的答案。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM