13

我听说你永远不应该抛出一个字符串,因为缺少信息,你会捕捉到你不希望捕捉到的异常。抛出异常的好习惯是什么?你继承一个基异常类吗?你有很多例外还是少数?你做 MyExceptionClass& 还是 const MyExceptionClass& ?等等。我也知道在析构函数中永远不应该抛出异常

我会补充一点,我了解合同设计以及何时抛出异常。我在问我应该如何抛出异常。

4

12 回答 12

12

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

给定这两个成员函数:

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

这些函数的名称以及它们的返回值向我表明,在FindApple的情况下,当找不到正确的苹果时,该函数完全能够返回 NULL,但在GetApple的情况下,您期望一个苹果返回. 如果第二个函数不能兑现它的承诺,它必须抛出一个异常。

异常适用于函数没有其他方式报告这些情况的异常情况。如果您决定将其作为承诺的一部分(阅读:函数签名),那么它可以报告该条件而不会引发异常。

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

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

最终,需要处理异常,但只能由知道如何以有用的方式处理特定条件的调用者来处理。我的意思是在最广泛的解释中:放弃的服务将在稍后重试,提供有用的错误消息的 UI,呈现“糟糕”屏幕但恢复良好的 Web 应用程序,......等等.

戴夫

于 2009-02-17T11:22:18.943 回答
9

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

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

于 2009-02-17T10:47:52.660 回答
4

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

于 2009-02-17T10:43:44.027 回答
3

有时可能会发生您无法返回错误代码的情况,例如。当您需要错误情况发生时的确切上下文时,例如。当您需要将错误状态向上传播 3 级时 - 您会失去上下文。

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

class DeviceException {
    ;
}

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

等等

然后,我可以根据类型和其中包含的信息来判断/采取行动。

于 2009-02-17T11:05:13.570 回答
1

我总是抛出一个异常,并给出它发生的位置以及导致它发生的原因的消息:

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

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

我总是通过

catch (NException& ex) { ... }

如果您运行 Windows,您可以传递错误值并让函数派生错误消息。最好的例子是Jeffrey Richter 在 Windows 中通过 C/C++ 编写的

于 2009-02-17T11:19:54.487 回答
1

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

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

  1. 异常对象的复制构造函数和析构函数都不能抛出异常。如果他们这样做,您的程序将立即终止。(ISO 15.5/1)

  2. 如果您的异常对象具有基类,则使用公共继承。仅当基类可访问
    时,才会为派生到基类选择处理程序。(ISO 15.3/3)

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

例如:

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
  }
}
于 2009-02-17T11:29:27.397 回答
1

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

捕获(标准::异常& e)
阻止并完成它。(尽管如果您不控制所有可以抛出的代码,通常您将无法摆脱这种情况)。

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

此外,您应该抛出一个临时变量并通过引用捕获它(以避免在您的捕获站点调用复制 ctor)。由于不清楚谁应该清理内存,因此也不允许抛出指针。C++ FAQ Lite也处理这个问题。

于 2009-02-18T00:38:34.253 回答
0

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

  1. 指示输入错误的数据错误。适当的操作是将消息保存到日志目录但不对其进行处理。
  2. 指示某些子组件(如输入队列、SQL 数据库、JNI 库)出现故障的基础设施错误。睡几分钟,然后重新连接。
  3. 指示某些方面配置不可行的配置错误。退出程序。

第一项是检查异常,因为我们认为数据检查是方法接口的一部分。其他的没有检查,因为主循环不能知道子组件的实现,例如,一个实现可能使用 SQL 数据库,或者可能只是将数据保存在内存中——调用者不需要知道。

于 2009-02-17T10:52:51.477 回答
0

正如已经说过的那样,仅将它们用于特殊情况。

始终为用户提供一种避免抛出异常的方法,例如。如果你有方法,如果出现这样的问题,它会抛出:

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

提供另一种方法供用户调用:

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

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

还要保持您的异常类层次结构平坦,并查看 InvalidStateException 和 ArgumentNullExcpetion 等标准异常。

于 2009-02-17T11:38:39.760 回答
0

如果您从其他开发人员将在下游使用的组件中抛出异常,请帮他们一个大忙,并始终从 std::exception 派生您自己的异常类(如果您真的需要它们,请参阅标准异常)。不惜一切代价避免像抛出整数、HRESULTS、char*、std::string 等完全可憎的事情......

于 2009-02-17T13:21:03.010 回答
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...*/
    }
}

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

于 2009-07-04T03:45:11.633 回答
0

来自 C++ FAQ,[17.12] 我应该扔什么?

一般来说,最好扔对象,而不是内置的。如果可能,您应该抛出(最终)从该类派生的类的实例std::exception

...和

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

于 2013-02-04T16:02:02.680 回答