我听说你永远不应该抛出一个字符串,因为缺少信息,你会捕捉到你不希望捕捉到的异常。抛出异常的好习惯是什么?你继承一个基异常类吗?你有很多例外还是少数?你做 MyExceptionClass& 还是 const MyExceptionClass& ?等等。我也知道在析构函数中永远不应该抛出异常
我会补充一点,我了解合同设计以及何时抛出异常。我在问我应该如何抛出异常。
我听说你永远不应该抛出一个字符串,因为缺少信息,你会捕捉到你不希望捕捉到的异常。抛出异常的好习惯是什么?你继承一个基异常类吗?你有很多例外还是少数?你做 MyExceptionClass& 还是 const MyExceptionClass& ?等等。我也知道在析构函数中永远不应该抛出异常
我会补充一点,我了解合同设计以及何时抛出异常。我在问我应该如何抛出异常。
在我看来,如果一个函数不能遵守它的“承诺”,如果它必须打破它的“合同”,它应该抛出一个异常。函数的签名(名称和参数)决定了它的合约。
给定这两个成员函数:
const Apple* FindApple(const wchar_t* name) const;
const Apple& GetApple(const wchar_t* name) const;
这些函数的名称以及它们的返回值向我表明,在FindApple的情况下,当找不到正确的苹果时,该函数完全能够返回 NULL,但在GetApple的情况下,您期望一个苹果返回. 如果第二个函数不能兑现它的承诺,它必须抛出一个异常。
异常适用于函数没有其他方式报告这些情况的异常情况。如果您决定将其作为承诺的一部分(阅读:函数签名),那么它可以报告该条件而不会引发异常。
请注意,在FindApple的情况下,由调用者决定如何处理“找不到合适的苹果”的情况,因为它不再是例外情况。
您可能很想尝试避免所有异常,但这意味着您必须考虑所有可能的异常情况,并且您将负担放在调用者身上。然后调用者需要检查“错误条件”。
最终,需要处理异常,但只能由知道如何以有用的方式处理特定条件的调用者来处理。我的意思是在最广泛的解释中:放弃的服务将在稍后重试,提供有用的错误消息的 UI,呈现“糟糕”屏幕但恢复良好的 Web 应用程序,......等等.
戴夫
一件基本的事情是只为特殊情况保留例外。不要将它们用于流量控制。例如,“找不到文件”不应该是异常,它应该是错误代码或返回值(除非文件是必须存在的东西,例如配置文件)。但是如果一个文件在你处理它的时候突然消失了,那么抛出一个异常是一个不错的选择。
谨慎使用异常时,您无需将代码转换为 try-catch -spaghetti 以避免从更深层接收到上下文中难以理解的异常。
使用标准例外!如果您有特定错误,请尝试使用返回值来避免它。如果必须使用异常,请定义继承自 Exception 的自定义异常并创建自定义消息。
有时可能会发生您无法返回错误代码的情况,例如。当您需要错误情况发生时的确切上下文时,例如。当您需要将错误状态向上传播 3 级时 - 您会失去上下文。
在这种情况下,自定义类是最好的解决方案。我使用这种方法,定义我自己的内联类(它们没有.cpp;只有.h),例如:
class DeviceException {
;
}
class DeviceIOException: public DeviceException {
DeviceIOException(std::string msg, int errorCode);
}
等等
然后,我可以根据类型和其中包含的信息来判断/采取行动。
我总是抛出一个异常,并给出它发生的位置以及导致它发生的原因的消息:
throw NException("Foo::Bar", "Mungulator cause a stack overflow!");
然后,您可以在消息框等中使用这些字符串。
我总是通过
catch (NException& ex) { ... }
如果您运行 Windows,您可以传递错误值并让函数派生错误消息。最好的例子是Jeffrey Richter 在 Windows 中通过 C/C++ 编写的。
抛出指针可能不是一件好事,因为它会使抛出对象的所有权复杂化。类类型异常可能比基础更好,因为它们可以包含有关异常原因的更多信息。
在使用类或类层次结构时,您应该考虑以下几点:
异常对象的复制构造函数和析构函数都不能抛出异常。如果他们这样做,您的程序将立即终止。(ISO 15.5/1)
如果您的异常对象具有基类,则使用公共继承。仅当基类可访问
时,才会为派生到基类选择处理程序。(ISO 15.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
}
}
你应该总是抛出一个从 std::exception 派生的异常类。这允许您的界面具有一定的一致性,并允许这些方法或函数的客户端具有更大的灵活性。例如,如果您想添加一个 catch all 处理程序,您可以添加一个
捕获(标准::异常& e)阻止并完成它。(尽管如果您不控制所有可以抛出的代码,通常您将无法摆脱这种情况)。
我倾向于只抛出标准提供的异常(即std::runtime_error),但如果你想为你的处理程序提供额外的粒度,你应该随意从std::exception派生出你自己的。请参阅C++ FAQ 精简版。
此外,您应该抛出一个临时变量并通过引用捕获它(以避免在您的捕获站点调用复制 ctor)。由于不清楚谁应该清理内存,因此也不允许抛出指针。C++ FAQ Lite也处理这个问题。
对于当前的项目,我们考虑了主程序循环可以采取的适当行动。基本程序接受 XML 消息,并将信息保存到数据库中(中间有相当数量的处理)。
第一项是检查异常,因为我们认为数据检查是方法接口的一部分。其他的没有检查,因为主循环不能知道子组件的实现,例如,一个实现可能使用 SQL 数据库,或者可能只是将数据保存在内存中——调用者不需要知道。
正如已经说过的那样,仅将它们用于特殊情况。
始终为用户提供一种避免抛出异常的方法,例如。如果你有方法,如果出现这样的问题,它会抛出:
public void DoSomethingWithFile() {
if(!File.Exists(..))
throw new FileNotFoundException();
}
提供另一种方法供用户调用:
public bool CanDoSomething() {
return File.Exists(..);
}
这样,调用者可以根据需要避免异常。如果出现问题,请毫不犹豫地抛出 - “快速失败”,但始终提供无异常路径。
还要保持您的异常类层次结构平坦,并查看 InvalidStateException 和 ArgumentNullExcpetion 等标准异常。
如果您从其他开发人员将在下游使用的组件中抛出异常,请帮他们一个大忙,并始终从 std::exception 派生您自己的异常类(如果您真的需要它们,请参阅标准异常)。不惜一切代价避免像抛出整数、HRESULTS、char*、std::string 等完全可憎的事情......
这是一个抛出几乎不占用任何资源的异常的简单示例:
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...*/
}
}
抛出的空类不占用任何资源或很少......
来自 C++ FAQ,[17.12] 我应该扔什么?:
一般来说,最好扔对象,而不是内置的。如果可能,您应该抛出(最终)从该类派生的类的实例
std::exception
。
...和
最常见的做法是抛出一个临时的:( 参见下面的示例)