如何抛出好的例外?

时间:2009-02-17 10:39:26

标签: c++ exception-handling throw

我听说你永远不应该抛出一个字符串,因为缺少信息,你会发现你不希望捕获的异常。抛出异常有什么好的做法?你继承了一个基本的异常类吗?你有很多例外或很少吗?你做MyExceptionClass&或const MyExceptionClass& ?此外,我知道永远不应该在析构函数中抛出异常

我将补充说,我理解按合同设计以及何时抛出异常。我问我应该如何抛出异常。

12 个答案:

答案 0 :(得分:12)

在我看来,如果一个函数不能保持其“承诺”,如果它必须打破它的“契约”,它应该抛出异常。函数的签名(名称和参数)决定了它的合同。

鉴于这两个成员函数:

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

这些函数的名称及其返回值向我表明,在 FindApple 的情况下,当找不到正确的apple时,该函数完全能够返回NULL,但在这种情况下 GetApple 你期待苹果回归。如果第二个函数不能保证其承诺,它必须抛出异常。

例外是指那些功能无法以其他方式报告这些条件的特殊情况。如果您决定将其作为承诺的一部分(读取:函数签名),那么它可以报告该条件而不会抛出异常。

请注意,在 FindApple 的情况下,由调用者决定如何处理“找不到合适的苹果”的情况,因为它不再是一种特殊情况。

您可能会试图避免所有异常,但这意味着您必须考虑所有可能的异常情况,并且您将负担放在调用方上。调用者需要检查“错误条件”。

最终,需要处理异常,但只有呼叫者知道如何以有用的方式处理特定条件。我的意思是最广泛的解释:放弃的服务将在稍后再试,提供有用的错误消息的UI,提供“oops”屏幕的网络应用程序,但恢复得很好,......等等

戴夫

答案 1 :(得分:9)

一个基本的事情是仅为特殊情况保留例外。不要将它们用于流量控制。例如,“找不到文件”不应该是例外,它应该是错误代码或返回值(除非文件是必须存在的东西,例如配置文件)。但是如果文件在处理时突然消失,那么抛出异常是一个不错的选择。

当谨慎使用异常时,您不需要将代码转换为try-catch -spaghetti,以避免从较深层接收难以理解的上下文异常。

答案 2 :(得分:4)

使用标准例外!如果您有特定错误,请尝试使用返回值来避免它。如果您 要使用例外,请定义从Exception继承并创建自定义消息的自定义异常。

答案 3 :(得分:3)

有时您可能无法返回错误代码,例如。当你需要出现错误情况时的确切背景,例如。当你需要传播错误状态3级时 - 你松散了上下文。

在这种情况下,自定义类是最佳解决方案。我使用这种方法,定义我自己的内联类(它们没有.cpp;只有.h)例如:

class DeviceException {
    ;
}

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

然后,我可以按类型和包含在其中的信息来判断/处理异常。

答案 4 :(得分:1)

我总是抛出一个异常,并附上一个消息,说明它发生的原因以及导致它发生的原因:

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

然后,您可以在消息框等中使用这些字符串。

我总是通过

抓住
catch (NException& ex) { ... }

如果您运行Windows,则可以传递错误值并使函数派生错误消息。最好的例子是Windows via C/C++ by Jeffrey Richter

答案 5 :(得分:1)

投掷指针可能不是一件好事,因为它使抛出对象的所有权变得复杂。类型类型异常可能比基本因素更好,因为它们可以包含有关异常原因的更多信息。

在使用类或类层次结构时,您应该考虑以下几点:

  1. 复制构造函数和 异常对象的析构函数 绝不能抛出异常。如果 他们你是程序会的 立即终止。(ISO 15.5 / 1)

  2. 如果你的异常对象有基础 类,然后使用公共继承 仅为a选择处理程序 如果基数派生到基类 class是可访问的。(ISO 15.3 / 3)

  3. 最后,(对于所有例外类型)确保 被抛出的表达式不能 本身导致抛出异常。

  4. 例如:

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

答案 6 :(得分:1)

你应该总是抛出一个派生自std :: exception的异常类。这允许您的界面具有一定的一致性,并允许这些方法或功能的客户端具有更大的灵活性。例如,如果要添加catch all处理程序,则可以添加

catch(std::exception& e)
块并完成它。 (尽管如果你不控制所有可以抛出的代码,你常常无法逃脱。)

我倾向于只抛出标准提供的异常(即std :: runtime_error),但是如果你想为处理程序提供额外的粒度,你可以随意从std :: exception派生自己的。请参阅the C++ FAQ lite

此外,您应该抛出一个临时的并通过引用捕获它(以避免在您的catch站点调用copy ctor)。投掷指针也不赞成,因为不清楚谁应该清理记忆。 C++ FAQ Lite也处理此事。

答案 7 :(得分:0)

对于当前项目,我们考虑了主程序循环可以采取的适当措施。基本程序接受XML消息,并将信息保存到数据库中(中间有大量处理)。

  1. 表示输入错误的数据错误。适当的操作是将消息保存到日志目录,但不处理它。
  2. 指示某个子组件(如输入队列,SQL数据库,JNI库)的基础结构错误发生故障。睡几分钟然后重新连接。
  3. 指示某些方面配置的配置错误不可行。退出程序。
  4. 第一项是检查异常,因为我们认为数据检查是方法接口的一部分。其他的是未经检查的,因为主循环不能知道子组件的实现,例如,实现可以使用SQL数据库,也可以简单地将数据保存在内存中 - 调用者不需要知道。

答案 8 :(得分:0)

正如之前所说,仅在特殊情况下使用它们。

始终为用户提供避免抛出异常的方法,例如。如果你有方法,那么如果出现问题就会抛出:

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

为用户提供另一种方法:

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

这样调用者可以在需要时避免异常。 如果出现问题,请不要犹豫 - “快速失败”,但始终提供无异常的路径。

同时保持您的异常类层次结构平坦,并查看标准异常,如InvalidStateException和ArgumentNullExcpetion。

答案 9 :(得分:0)

如果您正在从其他开发人员将使用下游的组件中抛出异常,请大家帮忙,并始终从std :: exception派生您自己的异常类(如果您确实需要使用标准异常的c.f)。不惜一切代价避免全面侮辱,例如投掷注册,HRESULTS,char *,std :: string ......

答案 10 :(得分:0)

以下是抛出几乎不占用任何资源的异常的简单示例:

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...*/
    }
}

抛出的空类不占用任何资源或很少...

答案 11 :(得分:0)

从C ++常见问题解答[17.12] What should I throw?

  

通常,最好抛出对象,而不是内置函数。   如果可能,您应该抛出类的实例   派生(最终)来自std::exception类。

...和

  

最常见的做法是暂时抛出:   (参见下面的示例)