我应该继承std :: exception吗?


98

我已经看到至少一个可靠的消息源(我采用了C ++类)建议C ++中特定于应用程序的异常类应继承自std::exception。我不清楚这种方法的好处。

在C#中,继承的原因ApplicationException很明确:您可以获得一些有用的方法,属性和构造函数,而只需添加或覆盖所需的内容即可。有了std::exception这一切似乎你得到的是一个what()覆盖方法,你也可以同样创造自己。

那么,将其std::exception用作特定于应用程序的异常类的基类有什么好处(如果有)?有什么好的理由不继承std::exception吗?



2
尽管作为一个与特定问题无关的旁注,但您采用的C ++类不一定非要凭自己的经验就可靠地掌握良好实践。
克里斯汀·劳

Answers:


69

主要的好处是使用类不必知道你确切类型的代码throw吧,但正好可以catchstd::exception

编辑:正如Martin和其他人指出的,您实际上想从header 中std::exception声明的子类之一派生<stdexcept>


21
无法将消息传递给std :: exception。std :: runtime_error接受一个字符串,并从std :: exception派生。
马丁·约克

14
您不应该将消息传递给异常类型构造函数(考虑到消息需要本地化。)相反,定义一个对错误进行语义分类的异常类型,在异常对象中存储格式化用户友好消息所需的内容,然后在捕获站点执行此操作。
埃米尔(Emil)

std::exception限定在所述<exception>头部(证明)。 -1
亚历山大·舒卡耶夫

5
那你有什么意思呢?定义意味着声明,您认为这里有什么错误?
Nikolai Fetissov '16

40

问题std::exception在于没有构造函数(在标准兼容版本中)接受消息。

因此,我更喜欢从导出std::runtime_error。这是从派生而来的,std::exception但其构造函数允许您将C-String或A std::string传递给在调用char const*时将返回(作为)的构造函数what()


11
在抛出时格式化用户友好的消息不是一个好主意,因为这会将低级代码与本地化功能结合在一起。而是将所有相关信息存储在异常对象中,并让捕获站点根据异常类型及其携带的数据来格式化一条用户友好的消息。
埃米尔(Emil)

16
@ Emil:如果您例外,请携带用户可显示的消息,尽管通常它们仅用于记录目的。在投掷站点上,您没有上下文来构建用户消息(因为无论如何这可能是一个库)。捕获站点将具有更多上下文,并且可以生成适当的味精。
马丁·约克

3
我不知道您是怎么想到异常仅用于记录目的的。:)
埃米尔(Emil)

1
@Emil:我们已经讨论了这个论点。我在这里指出的不是您要提出的例外。您对该主题的理解似乎不错,但是您的论点存在缺陷。为用户生成的错误消息与针对开发人员的日志消息完全不同。
马丁·约克

1
@Emil。这次讨论已有一岁了。此时创建您自己的问题。就我而言,您的论点是错误的(并且基于选票,人们倾向于同意我的观点)。请参阅为我的库实现多少“例外”?正赶上一般例外真的是一件坏事?自从您以前的糖尿病患者以来,您还没有提供任何nw信息。
马丁·约克

17

继承的原因std::exception是异常的“标准”基类,因此,团队中的其他人很自然,例如,期望并抓住基类std::exception

如果您正在寻找便利,则可以从std::runtime_error提供的std::string构造函数中继承。


2
除了std :: exception以外,从任何其他标准异常类型派生可能不是一个好主意。问题在于它们非标准地从std :: exception派生,这可能导致涉及多重继承的细微错误,其中catch(std :: exception&)可能会由于转换为std :: exception而悄无声息地无法捕获异常暧昧。
埃米尔(Emil)

2
我读了关于该主题的文章。从纯粹的逻辑意义上讲,这似乎是合理的。但是我从未在野外见过MH。我也不会考虑在例外情况下使用MH,因此解决不存在的问题似乎像是大锤。如果这是一个真正的问题,我相信我们会看到标准委员会对此采取的行动来修复这一明显的缺陷。因此,我认为std :: runtime_error(和家庭)仍然是引发或派生的完全可接受的异常。
马丁·约克

5
@Emil:除了其他标准例外,这std::exception是一个主意。您不应该做的是从多个继承。
Mooing Duck 2013年

@MooingDuck:如果您从一个以上的标准异常类型派生,您将最终得到std :: exception多次(非虚拟地)派生。参见boost.org/doc/libs/release/libs/exception/doc/…
埃米尔(Emil)

1
@Emil:这是我所说的。
Mooing Duck 2013年

13

我曾经参加过一个大型代码库的清理工作,以前的作者在这里扔了整数,HRESULTS,std :: string,char *,随机类……到处都是不同的东西。只要命名一个类型,它就可能被扔到某个地方。根本没有通用的基类。相信我,一旦我们弄清楚所有抛出的类型都有一个我们可以捕捉到的共同基础并且知道什么都不会过去的事情,事情就变得整洁了。因此,请帮自己(以及将来需要维护您代码的人)一个忙,并从一开始就这样做。


12

您应该继承boost :: exception。它提供了更多的功能和易于理解的方式来承载其他数据……当然,如果您不使用Boost,那么请忽略此建议。


5
但是请注意,boost :: exception本身不是从std :: exception派生的。即使从boost :: exception派生,您也应该从std :: exception派生(作为单独的注释,无论何时从异常类型派生,都应使用虚拟继承。)
Emil

10

是的,你应该从 std::exception

其他人已经回答了std::exception您不能向其传递文本消息的问题,但是,通常在抛出时尝试格式化用户消息不是一个好主意。而是使用异常对象将所有相关信息传输到捕获站点,然后捕获站点可以格式化用户友好的消息。


6
+1,好点。从关注点分离和i18n角度来看,最好让表示层构造用户消息。
John M Gant

@JohnMGant和Emil:有趣的是,您能指出一个有关如何完成此操作的具体示例。我知道可以从std::exception异常信息中获取并携带异常信息。但是谁将负责构建/格式化错误消息?该::what()功能或其他什么东西?
alfC 2014年

1
您可以在捕获站点格式化消息,很可能是在应用程序(而不是库)级别格式化消息。您可以按类型捕获它,然后对其进行探测以获取信息,然后对相应的用户消息进行本地化/格式化。
埃米尔(Emil)

9

之所以可能要从std::exception中继承,是因为它允许您引发根据该类捕获的异常,即:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

您应该了解的继承问题是对象切片。当您编写throw e;throw-expression时,将初始化一个称为异常对象的临时对象,该对象的类型是通过从操作数的静态类型中删除所有顶级cv限定符来确定的throw。那可能不是您所期望的。问题的示例,您可以在这里找到。

这不是反对继承的论据,而只是“必须知道”的信息。


3
我认为带走的是“抛出e”。是邪恶的,并且“抛出”;还可以
James Schek 09年

2
是的,throw;还可以,但是应该写这样的东西并不明显。
Kirill V. Lyadvinsky,09年

1
对于Java开发人员而言,使用“ throw e”进行重新抛出特别痛苦。
James Schek 09年

考虑仅从抽象基本类型派生。按值捕获抽象基类型将给您一个错误(避免切片);通过引用捕获抽象的基本类型,然后尝试抛出它的副本将给您一个错误(再次避免切片。)
Emil

您还可以通过添加一个成员函数来解决此问题raise(),例如:virtual void raise() { throw *this; }。只要记住在每个派生异常中重写它即可。
贾斯汀时间-恢复莫妮卡

5

区别:std :: runtime_error与std :: exception()

无论你应该从它继承与否是取决于你。Standard std::exception及其标准后代提出了一种可能的异常层次结构(分为子logic_error层次和子runtime_error层次)和一种可能的异常对象接口。如果您喜欢它,请使用它。如果出于某些原因您需要其他内容,请定义自己的异常框架。


3

由于该语言已经引发了std :: exception,因此无论如何都需要捕获它以提供体面的错误报告。您也可以将同一捕获用于您自己的所有意外异常。而且,几乎所有引发异常的库都将从std :: exception派生它们。

换句话说,无论是

catch (...) {cout << "Unknown exception"; }

要么

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

第二种选择肯定更好。


3

如果所有可能的异常都来自std::exception,则catch块可以简单地catch(std::exception & e)并确保捕获所有内容。

捕获异常后,可以使用该what方法获取更多信息。C ++不支持鸭子类型,因此带有what方法的另一个类将需要不同的catch和不同的代码才能使用它。


7
不要将std :: exception子类化,然后捕获(std :: exception e)。您需要捕获对std :: exception&(或指针)的引用,否则您将切出子类数据。
jmucchiello,2009年

1
现在这是一个愚蠢的错误-当然我知道得更多。stackoverflow.com/questions/1095225/...
马克赎金

3

是否从任何标准异常类型派生是第一个问题。这样做可以为所有标准库异常和您自己的异常处理程序启用单个异常处理程序,但同时也鼓励此类“全部捕获”处理程序。问题是,一个人应该只捕获一个知道如何处理的异常。例如,在main()中,如果在退出前将what()字符串作为最后手段记录下来,则捕获所有std :: exceptions可能是一件好事。但是,在其他地方,这不是一个好主意。

一旦决定是否从标准异常类型派生,那么问题是哪个应该作为基础。如果您的应用程序不需要i18​​n,则您可能会认为在呼叫站点格式化消息与在呼叫站点保存信息并生成消息一样好。问题是可能不需要格式化的消息。最好使用惰性消息生成方案-可能带有预分配的内存。然后,如果需要该消息,它将在访问时生成(并且可能缓存在异常对象中)。因此,如果在引发消息时生成了消息,则需要一个std :: exception派生类,如std :: runtime_error作为基类。如果消息是延迟生成的,则std :: exception是适当的基础。


0

子类异常的另一个原因是在大型封装系统上工作时更好的设计方面。您可以将其重用于验证消息,用户查询,致命控制器错误等内容。无需重写或重新获得消息之类的所有验证,您只需将其“捕获”在主源文件中,而是将错误抛出整个类集中的任何地方。

例如,致命异常将终止程序,验证错误将仅清除堆栈,并且用户查询将向最终用户询问问题。

这样做还意味着您可以在不同的接口上重用相同的类。例如,Windows应用程序可以使用消息框,Web服务将显示html,报告系统将其记录,依此类推。


0

尽管这个问题比较老,并且已经得到了很多答案,但是我只想添加有关如何在C ++ 11中进行适当的异常处理的注释,因为在有关异常的讨论中,我一直都缺少这个问题:

使用std::nested_exceptionstd::throw_with_nested

它在此处此处的 StackOverflow上进行描述,如何通过简单地编写适当的异常处理程序(该异常处理程序将重新抛出嵌套的异常)来获得对代码内异常的追溯,而无需调试程序或繁琐的日志记录。

由于您可以使用任何派生的异常类执行此操作,因此可以向此类回溯中添加很多信息!您也可以在GitHub上查看我的MWE,回溯显示如下:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

您甚至不需要继承 std::runtime_error抛出异常时,即可获取大量信息。

在子类化中(而不是仅使用std::runtime_error),我看到的唯一好处是您的异常处理程序可以捕获您的自定义异常并执行一些特殊的操作。例如:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.