简体   繁体   English

如何抛出良好的异常?

[英]How to throw good exceptions?

I heard you should never throw a string because there is a lack of information and you'll catch exceptions you dont expect to catch. 我听说您永远不要抛出字符串,因为缺少信息,并且您会捕获到您不希望捕获的异常。 What are good practice for throwing exceptions? 引发异常的良好做法是什么? do you inherit a base exception class? 您继承基本异常类吗? Do you have many exceptions or few? 你有例外还是很少? do you do MyExceptionClass& or const MyExceptionClass& ? 你做MyExceptionClass&或const MyExceptionClass&吗? etc. Also i know exceptions should never been thrown in destructors 等。我也知道异常不应该在析构函数中抛出

i'll add that i understand design by contract and when to throw exception. 我还要补充一点,我了解合同规定的设计以及何时引发异常。 I am asking how i should throw exceptions. 我在问我该如何抛出异常。

In my opinion, a function should throw an exception if it can't keep its "promise", if it has to break its "contract". 我认为,如果某个函数无法履行其“承诺”,并且必须打破其“契约”,则应该抛出一个异常。 The function's signature (name and parameters) determine its contract. 函数的签名(名称和参数)确定其合同。

Given these two member functions: 给定这两个成员函数:

const Apple* FindApple(const wchar_t* name) const;
const Apple& GetApple(const wchar_t* name) const;

The names of these functions as well as their return values indicate to me that in the case of FindApple the function is perfectly capable of returning NULL when the correct apple was not found, but in the case of GetApple you're expecting an apple to return. 这些函数的名称以及它们的返回值向我表明,在FindApple的情况下,当找不到正确的苹果时,该函数完全能够返回NULL,但是在GetApple的情况下,您期望一个苹果返回。 If that second function can't keep its promise, it must throw an exception. 如果第二个函数不能履行承诺,则必须抛出异常。

Exceptions are meant for those exceptional conditions in which a function has no other way of reporting these conditions. 异常适用于功能无法用其他方式报告这些条件的异常条件。 If you decide to make it a part of the promise (read: function signature) then it can report that condition without throwing an exception. 如果您决定将其作为Promise的一部分(读取:函数签名),则它可以报告该条件而不会引发异常。

Note that in the case of FindApple , it's up to the caller to decide how to handle the condition of "not finding the right apple" because it's no longer an exceptional condition. 请注意,在FindApple的情况下,由调用者决定如何处理“找不到合适的苹果”的条件,因为它不再是特殊情况。

You might be tempted to try to avoid all exceptions, but that means you have to account for all possible exceptional conditions, and you're placing the burden on the caller instead. 您可能很想避免所有异常,但这意味着您必须考虑所有可能的特殊情况,而这将负担加在了调用方身上。 The caller needs to check for "error conditions" then. 然后,呼叫者需要检查“错误条件”。

Ultimately, an exception needs to be handled, but only by the caller that knows how to handle a particular condition in a useful way . 最终,需要处理异常,但是只有知道如何如何以有用方式处理特定条件的调用方才能处理 And I mean this in the widest possible interpretation: a service that gives up will try again later, a UI that provides a helpful error message, a web app that presents a "oops" screen but that recovers nicely, ... and so on. 我的意思是,在尽可能广泛的解释中:放弃的服务将在稍后重试,提供有用错误消息的UI,显示“哎呀”屏幕但恢复良好的Web应用程序,等等。 。

Dave 戴夫

One basic thing is to reserve exceptions for exceptional situations only. 基本的一件事是只为特殊情况保留例外。 Don't use them for flow control. 不要将它们用于流量控制。 For instance, "file not found" should not be an exception, it should be an error code or return value (unless the file is something that must exist, eg a configuration file). 例如,“找不到文件”不应该是例外,它应该是错误代码或返回值(除非文件必须存在,例如配置文件)。 But if a file suddenly disappears while you're processing it, then throwing an exception is a good choice. 但是,如果在处理文件时文件突然消失,则抛出异常是一个不错的选择。

When exceptions are used sparingly, you don't need to turn your code into a try-catch -spaghetti in order to avoid receiving incomprehensible-in-the-context exceptions from the deeper layers. 当很少使用异常时,您无需将代码转换为try-catch -spaghetti,从而避免从更深的层次接收上下文中无法理解的异常。

Use the standard exceptions! 使用标准例外! If you have a specific error, try to avoid it with return value. 如果有特定错误,请尝试避免使用返回值。 If you have to use exceptions, define your custom exception that inherits from Exception and create a custom message. 如果必须使用异常,请定义从Exception继承的自定义异常并创建自定义消息。

Sometimes it can happen that you're not able to return error code eg. 有时可能会发生无法返回错误代码的情况。 when you need exact context of when error situation occured, eg. 当您需要错误情况发生时的确切上下文时,例如 when you need to propagate error status 3 levels up - you loose context. 当您需要将错误状态传播到3级时-您会失去上下文。

In this situation custom class is the best solution. 在这种情况下,自定义类是最好的解决方案。 I use this approach, defining my own inline classes (there's no .cpp for them; only .h) eg.: 我使用这种方法,定义了自己的内联类(它们没有.cpp;只有.h),例如:

class DeviceException {
    ;
}

class DeviceIOException: public DeviceException {
    DeviceIOException(std::string msg, int errorCode);
}

etc. 等等

I then can judge/act upon the exception by type and by information contained within. 然后,我可以根据类型和包含在其中的信息对异常进行判断/采取行动。

I always throw an exception with a message of where it occurred and what caused it to happen: 我总是抛出异常,并给出异常发生的位置以及导致它发生的原因的消息:

throw NException("Foo::Bar", "Mungulator cause a stack overflow!");

You can then use these strings in messageboxes etc. 然后,您可以在消息框等中使用这些字符串。

I always catch via 我总是通过

catch (NException& ex) { ... }

If you running windows you can pass the error value and have a function derive the error message. 如果您正在运行Windows,则可以传递错误值并让函数派生错误消息。 The best example of this is in Windows via C/C++ by Jeffrey Richter . 最好的例子是Jeffrey RichterWindows中通过C / C ++编写的

Throwing pointers is probably not a good thing, as it complicates ownership of the thrown object. 抛出指针可能不是一件好事,因为它会使所抛出对象的所有权变得复杂。 Class type exceptions are probably better than fundamentals simply because they can contain more information about the reason for the exception. 类类型异常可能比基本类型更好,仅仅是因为它们可以包含有关异常原因的更多信息。

In using a class or class hierarchy there are a couple of points you should consider: 使用类或类层次结构时,应考虑以下几点:

  1. Both the copy constructor and destructor of the exception object must never throw an exception. 异常对象的复制构造函数和析构函数都不得抛出异常。 If they do you're program will terminate immediately.(ISO 15.5/1) 如果这样做,您的程序将立即终止。(ISO 15.5 / 1)

  2. If your exception objects have base classes, then use public inheritance. 如果异常对象具有基类,则使用公共继承。
    A handler will only be selected for a derived to base class if the base class is accessible .(ISO 15.3/3) 仅在可访问基类的情况下,才为派生到基类的对象选择一个处理程序。(ISO 15.3 / 3)

  3. Finally, (for all exception types) ensure that the expression being thrown cannot itself result in an exception being thrown. 最后,(对于所有异常类型)确保所引发的表达式本身不会导致引发异常。

For example: 例如:

class Ex {
public:
  Ex(int i) 
  : m_i (i)
  {
    if (i > 10) {
      throw "Exception value out of range";
    }
  }

  int m_i;
};


void foo (bool b) {
  if (! b) {
     // 'b' is false this is bad - throw an exception
     throw Ex(20);    // Ooops - throw's a string, not an Ex
  }
}

You should always throw an exception class derived from std::exception. 您应该始终抛出从std :: exception派生的异常类。 This allows a certain consistency to your interface and allows more flexibility to the clients of these methods or functions. 这使您的界面具有一定的一致性,并为这些方法或函数的客户端提供了更大的灵活性。 For example if you want to add a catch all handler you may be able to add a 例如,如果您想添加一个全部捕获处理程序,则可以添加一个

catch(std::exception& e)
block and be done with it. 阻止并完成它。 (Though often you won't be able to get away with that if you don't control all the code that can throw). (尽管如果不控制所有可能抛出的代码,通常无法摆脱这种情况)。

I tend to throw only exceptions provided by the the standard (ie std::runtime_error) but if you want to provide extra granularity to your handlers, you should feel free to derive your own from std::exception. 我倾向于只抛出标准提供的异常(即std :: runtime_error),但是如果您想为处理程序提供额外的粒度,则可以随意从std :: exception派生自己的异常。 See the C++ FAQ lite . 请参阅C ++ FAQ精简版

Also, you should throw a temporary and catch it by reference (to avoid the copy ctor be invoked at your catch site). 另外,您应该抛出一个临时文件并通过引用对其进行捕获(以避免在捕获站点上调用复制ctor)。 Throwing pointers is also frowned upon since it is unclear who should clean up the memory. 由于不清楚谁应该清理内存,因此也抛出了指针。 C++ FAQ Lite deals with this too. C ++ FAQ Lite也处理这个问题

Here is a simple example of throwing an exception that takes barely any resources: 这是引发几乎不占用任何资源的异常的简单示例:

class DivisionError {};
class Division
{
public:
    float Divide(float x, float y) throw(DivisionError)
    {
        float result = 0;
        if(y != 0)
            result = x/y;
        else
            throw DivisionError();
        return result;
    }
};

int main()
{
    Division d;
    try
    {
        d.Divide(10,0);
    }
    catch(DivisionError)
    {
        /*...error handling...*/
    }
}

The empty class that is thrown does not take any resource or very few... 抛出的空类不会占用任何资源或占用很少资源。

From the C++ FAQ, [17.12] What should I throw? 从C ++常见问题解答中, [17.12]我应该扔什么? :

Generally, it's best to throw objects, not built-ins. 通常,最好抛出对象,而不是内置对象。 If possible, you should throw instances of classes that derive (ultimately) from the std::exception class. 如果可能,您应该抛出(最终)从std::exception类派生的类的实例。

...and ...和

The most common practice is to throw a temporary: (see example that follows) 最常见的做法是抛出一个临时值:( 请参见下面的示例)

For a current project, we thought about the appropriate action that could be taken by the main program loop. 对于当前项目,我们考虑了主程序循环可以采取的适当操作。 The basic program accepts XML messages, and saves the information into a database (with a fair amount of processing in between). 基本程序接受XML消息,并将信息保存到数据库中(中间有大量处理)。

  1. Data errors that indicate something wrong the input. 表示输入错误的数据错误。 Appropriate action is to save the message to a log directory but not process it. 适当的操作是将消息保存到日志目录,但不对其进行处理。
  2. Infrastructure errors that indicate some subcomponent (like the input queue, an SQL database, a JNI library) is malfunctioning. 指示某些子组件(例如输入队列,SQL数据库,JNI库)的基础结构错误正在发生故障。 Sleep for a few minutes then reconnect. 睡眠几分钟,然后重新连接。
  3. Configuration errors that indicate some aspect configuration is unworkable. 指示某些方面配置的配置错误不可行。 Exit the program. 退出程序。

The first item is a checked exception, since we considered data checking to be part of a method's interface. 第一项是已检查的异常,因为我们认为数据检查是方法接口的一部分。 The others are unchecked since the main loop cannot know the implementations of subcomponents, eg an implementation may use an SQL database, or may simply persist data in memory -- the caller doesn't need to know. 由于主循环无法知道子组件的实现,因此其他选项不受检查,例如,一个实现可以使用SQL数据库,或者可以仅将数据持久存储在内存中-调用者不需要知道。

As it has been already said use them for exceptional situations only. 如前所述,仅在特殊情况下使用它们。

Always provide a way for the user to avoid throwing an exception, eg. 始终为用户提供一种避免引发异常的方法,例如。 if you have method, that will throw if something goes wrong like this: 如果您有方法,则在出现以下问题时将抛出该错误:

public void DoSomethingWithFile() {
    if(!File.Exists(..))
        throw new FileNotFoundException();
}

Provide another method for the user to call: 为用户提供另一种调用方法:

public bool CanDoSomething() {
    return File.Exists(..);
}

This way there the caller can avoid exceptions if he wants. 这样,呼叫者可以在需要时避免异常。 Do not hesitate to throw if something is wrong - "fail fast", but always provide exception-free path. 如果出现问题,请不要犹豫,“快速失败”,但始终提供无异常的路径。

Also keep your exception class hierarchy flat and take a look at the standard exceptions like InvalidStateException and ArgumentNullExcpetion. 此外,请保持您的异常类层次结构平坦,并查看诸如InvalidStateException和ArgumentNullExcpetion之类的标准异常。

If you're throwing exceptions from a component other developers will be using downstream, please do them a big favour and always derive your own exception classes (if you really need them cf using the standard exceptions) from std::exception. 如果您从其他开发人员将在下游使用的组件抛出异常,请帮他们一个忙,并始终从std :: exception派生您自己的异常类(如果您确实需要使用标准异常来比较它们)。 Avoid at all costs utter abominations like throwing ints, HRESULTS, char*, std::string... 避免不惜一切代价避免发生可恶的行为,例如抛出整数,HRESULTS,char *,std :: string ...

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

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