C ++在现实世界中有没有例外的情况?[关闭]


40

何时使用C上的C和C上的C ++?有一个声明WRT。编码大小/ C ++异常:

杰里的答案(除其他外):

(...)使用C ++生成真正微小的可执行文件往往会更加困难。对于非常小的系统,无论如何您几乎不会编写很多代码,而额外的(...)

我问为什么会这样,杰里对此回答:

最主要的是C ++包含了异常处理,这(至少通常是)为可执行文件的大小增加了一些最小值。大多数编译器将允许您禁用异常处理,但是当您执行此操作时,结果将不再是C ++。(...)

在技​​术的现实世界中,我对此并不怀疑。


因此,我有兴趣(纯粹出于好奇)从现实世界的示例中听到,在该示例中,一个项目选择C ++作为语言,然后选择禁用异常。(不仅在用户代码中“不使用”异常,而且在编译器中将其禁用,这样您就不能引发或捕获异常。)为什么一个项目选择这样做(仍然使用C ++而不是C,但是没有)例外)-(技术上的)原因是什么?


附录:对于那些希望详细说明答案的人,很高兴详细说明如何处理无例外的含义:

  • STL集合(vector,...)无法正常工作(无法报告分配失败)
  • new 不能扔
  • 建设者不能失败

1
JSF C ++。喷气机和例外不会混在一起。
编码员

1
有关异常(和一般C ++)问题的一些启发性信息250bpm.com/blog:4
Ambroz Bizjak 2012年

1
@AmbrozBizjak-我将在您链接到的帖子的评论中引用DeadMG:quote:“在我看来,您不了解异常。” 我同意。显然,作者查看了他在那篇文章中提供的示例,弄得一团糟的异常处理。
马丁·

Answers:


35

即使在今天,几乎所有的控制台游戏都在C ++中启用,但异常已禁用。实际上,这是针对那些控制台的C ++编译器的默认设置。有时,不能保证某些C ++功能可以在那些编译器上正常工作,例如多重继承(例如,我正在考虑一个非常知名的控制台默认编译器)。


另外,另一个示例是Arduino硬件SDK usnig gcc,无例外地在C ++中激活,以及诸如未提供STL之类的其他事情


有一些技术原因,无论好坏,无论如何,这不是我的建议,而是我听到的原因:

  1. 大多数控制台实际上是具有有限内存和处理时间的嵌入式系统。也许在将来的游戏机中并非如此,但是与PC相比,当前的游戏机仍具有一定的限制。通常,某些便携式控制台比任何智能手机(例如NDS)都难于编程。即使您不使用例外功能,它也确实会增加内存并增加一点速度成本。您可以检查自己,即使在PC上也是如此。
  2. 控制台上的视频游戏不会崩溃。必须对它们进行测试,以免发生任何崩溃或任何死角,任何展示台。这就是游戏机制造商要求在发布游戏之前对游戏进行严格检查的原因。这也意味着异常管理会增加成本,而这对于主机游戏来说并没有多大用处。例如,在智能手机中会更好,因为可能会有一些恢复方法或添加一些代码通过电子邮件向您发送问题。像大多数控制台一样的封闭平台不允许这样做。因此,毕竟不是真正需要异常系统。您“只需使其正常工作”。;)
  3. 当您不允许错误和崩溃时,异常管理意味着您必须实施错误管理策略。这样的系统可能足够复杂,以致使某人花费大量时间使其变得有用。游戏开发人员没有足够的精力去开发在崩溃时有用的功能...
  4. 编译器不允许(至今)。是的,它发生了。

我认为即使在游戏中例外也可能有用,但这确实是在主机游戏上并没有真正的用处。


更新:

我在这里添加另一个令人惊讶的示例:LLVM / CLang 出于以下原因不使用异常或RTTI

为了减少代码和可执行文件的大小,LLVM不使用RTTI(例如dynamic_cast <>)或异常。这两种语言功能违反了一般的C ++原则,即“您只为所用内容付费”,即使在代码库中从未使用过异常,或者在类中从未使用过RTTI,这也会导致可执行文件膨胀。因此,我们在代码中全局关闭了它们。

也就是说,LLVM确实大量使用了手动滚动的RTTI形式,该形式使用了isa <>,cast <>和dyn_cast <>之类的模板。这种形式的RTTI是可选的,可以添加到任何类中。它也比dynamic_cast <>更有效。

CLang以其快速的编译速度和显式错误而闻名,但它也是一种罕见的编译器,具有真正易于遵循的代码。


9
(3):无论如何,您都需要一种错误管理策略,如果您要继续使用(2)。问题将会出现,并且您的控制台软件需要轻而易举地失败。对我来说,显而易见的是,完全避免例外会使一切变得容易。
David Thornley

6
严格控制的运行时环境的所有出色示例,在这些环境中,错误得到正确处理并且没有“异常”事件。
Patrick Hughes

1
@DavidThornley因为您假定控制台的系统将管理某种错误,但不会。好吧,也许它将在PS3或XBox上使用,但将不允许其通过控制台制造商的测试。无论如何,大多数高级控制台的固件并不像您想象的那样灵活。控制台游戏几乎需要访问控制台的所有硬件,因此您可以考虑在控制台上运行的游戏,就像它也是控制台的“ OS”一样……我们拥有功能强大的控制台,越多,越不正确。因此,我同意你的观点,在某些情况下这似乎很奇怪。
克拉姆2011年

2
这是一个很好的答案,我还要补充一点,即使出现异常并向用户显示错误,他或她应该用D-pad做什么呢?最终用户唯一真正的解决方案是重新启动。
匿名

2
我删除了这个愚蠢的例子,因为他们的团队中有人说“不例外”并非意味着“不使用例外”,而是“不违反规则”。参见permalink.gmane.org/gmane.comp.lib.boost.devel/231377
Klaim 2012年

9

杰里说:...结果不再是C ++了,而我的隐喻是它显然 C ++,只是略有不同的方言,因为程序利用了其他形式,约定和书面样式。

这是我禁用它们的主要原因:

二进制兼容性

跨越语言和翻译的界限并不是普遍定义明确或不确定的。如果要保证程序在定义的行为范围内运行,则需要在模块出口处隔离异常。

可执行文件大小

这是我编写的无异常程序的二进制大小,该程序在不启用和启用异常的情况下构建:

无例外:

  • 可执行文件+依赖项:330
  • 最终剥离的可执行文件(发布版本):37

例外情况:

  • 可执行文件+依赖项:380
  • 最终剥离的可执行文件(发布版本):44

提醒:这是包含零抛出/捕获的库和程序的集合。编译器标志确实启用了C ++标准库中的异常。因此,在此示例中看到的实际成本超过19%。

编译器:apple gcc4.2 + llvm。大小以MB为单位。

速度

尽管有术语“零成本例外”,但即使没有抛出任何异常,它们仍然会增加一些开销。在上述情况下,它是一个性能至关重要的程序(信号处理,生成,演示,转换,以及大数据集/信号等)。异常不是此设计中的必要功能,而性能非常重要。

程序正确性

似乎是一个奇怪的原因...如果不能选择抛出,则必须编写相对严格,正确,经过良好测试的程序,以确保您的程序正确执行,并且确保客户端正确使用接口(如果给我一个错误的参数或而不检查错误代码,则应得到UB)。结果?实现质量大大提高,问题很快得到解决。

简单

异常处理实现并不经常保持最新。它们还增加了很多复杂性,因为实现可能具有许多许多退出序列。当程序使用一小组定义良好,类型明确的退出策略,这些策略冒泡到客户端并由客户端处理时,则更易于阅读和维护。在其他情况下,随着时间的推移,这些实现可能会实现更多的抛出,或者它们的依赖性可能会引入它们。客户无法轻易或适当地防御所有这些退出。我编写和更新了很多库,并且有频繁的发展和改进。尝试使所有异常与异常退出序列(在大型代码库中)保持同步不会很好地利用时间,并且可能会增加很多噪音和混乱。由于提高了程序的正确性和更多的测试,

历史/现有代码

在某些情况下,由于历史原因从未引入过它们。现有的代码库没有使用它们,因为约定和实现方面的重叠,更改程序可能要花很多年的时间,并且很难维护。

缺点

当然,也有缺点,最大的缺点是:与其他库的不兼容性(包括二进制),以及您必须实施大量程序以适合此模型的事实。


+1好信息!(尽管我不同意“简洁性”部分)
Martin Ba

如果您只有无故障构造函数以及如何处理分配(-故障),那将很有趣。
马丁·巴

@马丁1)意见分歧很好。我了解到,大多数开发人员不同意禁用异常是出于多种原因。为简单起见,它与程序的正确性一起出现。问题/无效状态根本不允许走得太远。这意味着他们检查故障并正常退出。jheriko的帖子与此相呼应。
贾斯汀

1
@Martin 2b)分配更为复杂。首先,堆分配的数量减少且大小增加-开始时堆分配的数量相对较少。其次,分配通过自定义分配器进行。如果最初没有为分配器提供分配(例如mallocreturn 0),则分配器将进入while (1)具有上下文切换的,然后进行另一次分配尝试。在此代码库中,过去通常是通过分配器返回的新值可以返回0,但较新的实现却可以正常工作。(续)
贾斯汀

2
是的,在自定义分配器接口上有一个与Stl兼容的包装器。从技术上讲,该程序不会在发布时中止;它将引入上下文切换(例如,允许另一个线程工作以有望释放一些内存),重试并记录失败的日志,直到永远。单元测试可以运行几天而不会出现问题。唯一的真正威胁是庞大的拨款,其中很多将在请求之前被捕获。同样,此时系统的故障率也在关注之中。在这种情况下,它们有可能在核心实现之前失败。
贾斯汀

7

Google不批准其C ++样式指南中的例外,主要是出于历史原因:

从表面上看,使用例外的好处超过了成本,尤其是在新项目中。但是,对于现有代码,异常的引入会影响所有从属代码。如果可以将异常传播到新项目之外,那么将新项目集成到现有的无异常代码中也将成为问题。由于Google现有的大多数C ++代码都不准备处理异常,因此采用产生异常的新代码相对困难。

鉴于Google现有的代码不是容错的,因此使用例外的成本要比新项目中的成本高一些。转换过程将很慢且容易出错。我们认为错误代码和断言之类的异常替代方法不会带来很大的负担。

我们关于使用异常的建议并非基于哲学或道德依据,而是基于实践依据。因为我们想在Google使用我们的开源项目,而且如果这些项目使用例外情况也很难这样做,所以我们也需要在Google开源项目中针对例外情况提出建议。如果我们必须从头开始重新做一遍,事情可能会有所不同。

Windows代码有一个例外(无双关语)。

(编辑重点)


5

Qt几乎从不使用异常。Qt中的错误由错误代码和信号表示。正式声明的原因是:

启动Qt时,并非所有需要Qt支持的编译器都有异常。今天,我们试图使API保持一致,因此具有不使用异常历史记录的模块通常不会使用添加的异常来获取新代码。

为什么QT使用很少的例外?

今天,对Qt的一种普遍批评是它的异常安全性还不完善。


1
谢谢。好信息,虽然我想知道大多数QT代码库是否可能在编译器中启用了例外...
Martin Ba

4

Symbian C ++(在某些诺基亚手机上使用)没有使用异常,至少没有直接使用异常,因为当Symbian最初开发时,C ++编译器没有可靠地实现它们。


1
这不适用于现代版本的Symbian。Symbian TRAP和 leave 被实现为真正的C ++异常,但是EPOC32和较早版本的Symbian确实依赖于其他方式(如果我没有记错的话,请使用setjmp / longjmp)。
otto

1
@OttoHarju:这就是我的意思,“至少不是直接”。
基思·汤普森

3

我/从不/使用异常。造成这种情况的原因很多,但是两个主要原因是,我从不需要它们来生成健壮的代码,并且它们降低了运行时的性能。

我研究了同时使用和禁用异常的生产代码-允许异常的代码一向更糟。在某些地方,异常用于真正的流控制而不是错误处理,这是非常重的,反性能且难以调试的。通常,在异常填充代码中进行调试的问题比在无异常代码中进行调试要困难得多-部分归结于堆栈和异常机制的内在困难,但要远远超出其原因是因为鼓励使用惰性代码提供异常处理的结果。

异常本身/如果您不关心性能/也没有时间做适当的事情,那没有什么大不了的错-它们是错误处理的语言功能,并且是适当错误处理机制的良好替代品。但是,几乎总是有/ better /的错误处理方式-就像您自己的逻辑一样(很难详细说明-这几乎是常识)。如果不是您的错误,而是来自库(例如,标准库),则您必须尊重异常的选择或崩溃,但是我总是会质疑这种选择。我从未见过这样的情况:例外实际上是最好的解决方案。

断言更易于调试,错误代码的重量更轻...在两者之间,如果使用得当,您将更易于阅读,调试和维护代码,速度更快。全面胜出...


1
不。滥用异常会更容易,但除此之外,异常还能很好地发挥作用。假设您的所有功能都是异常安全的(至少通常要对所有资源都使用RTTI,这是您应该做的事情),所以这至少很容易推论。正确地执行错误代码可能非常繁琐,并且可能导致将程序埋入大量错误处理代码中。在这种情况下,例外会更好。请记住,我的做法与您不同,这不是确凿的证据表明我不在乎正确的做法。
David Thornley

...关于为何RTTI不好,这又是另一回事了-我所见过的每个编译器中的内置RTTI都是无与伦比的-如果您甚至需要RTTI。但是,所有有关上下文的信息-RTTI和异常之类的东西对于RAD和企业应用程序可能非常有用-在高性能(例如游戏)计算领域中,它们没有地位。我从未见过将生产游戏引擎用于任何可能被关闭的目标……
jheriko 2011年

2
抱歉-RAII是我的意思。如果您不使用它,那么不仅异常会变得混乱。(不过,动态类型转换在我的工作中效果很好,而且我确实必须关注性能。)
David Thornley

有趣的话题:我认为异常允许使用更简洁的错误处理代码,但是错误代码更健壮,因为它们允许本地处理错误(您可以在调用方中立即检查返回代码,而异常可以在捕获到它们之前使调用栈冒泡有时它们不会被捕获,从而导致崩溃。除非您使用catch(...)。
Giorgio

3

在Bjarne等人的《联合打击战斗机C ++编码标准》中。此外,由于战斗机的实时性要求很高,因此禁止例外。

JSF ++用于硬实时和对安全至关重要的应用程序(飞行控制软件)。如果计算时间过长,则可能会导致死亡。因此,我们必须保证响应时间,而在当前的工具支持水平下,我们不能为例外情况做到这一点。在这种情况下,甚至免费商店的分配也被禁止!实际上,JSF ++有关错误处理的建议是在我们拥有正确执行工具(即使用异常)的日子来模拟异常的使用。

引用自Bjarne的C ++常见问题解答

请记住,C ++可能运行所有语言中种类最多的软件...


JSF ++也禁止使用“ new”(除放置new之外)和“ delete”。这是对“您如何无例外地处理新的失败”这一问题的一个答案……
armb

@armb:是的。请参阅“在这种情况下,甚至禁止免费分配商店!” 以上。
Macke

2

在C ++库设计中这很重要。通常,使用C接口时,将异常从第三方库抛出给客户端是很讨厌的。关键是,如果您的库抛出异常,那么您将淘汰一组将使用该库的客户端,因为它具有不抛出保证(无论出于何种原因客户端对异常进行限制)。

就个人而言,当团队被告知“发生异常”时,异常就会被滥用。当然,您会在这里看到错误-在有人弄清楚该怎么办之前,该代码已引发异常。该项目发生了一些崩溃,因为这些异常情况偶尔会出现。


1

也许在这方面,值得一提的是Embedded C ++。嵌入式C ++是为嵌入式系统设计的C ++的一种变体(显然足够了)。它基本上是C ++的适当子集,(除其他事项外)除去了模板,名称空间和异常处理。

我应该补充一点,尽管EC ++刚推出时就引起了轰动,但它们似乎基本上都变得安静了。我不确定人们是否对它失去了兴趣,或者他们的第一次尝试是如此完美,以至于十年左右没有人发现有任何理由对此感到困惑。<closed captioning for the humor impaired>Yeah, right!</closed captioning>


寻找问答-caravan.net/ec2plus/question.html-我要说这已经死了。
马丁·巴

1

内核模式编程就是一个很好的例子。

您可以在那里使用C ++以获得更清晰的代码,但没有例外。没有针对它们的运行时库,异常处理使用了太多的堆栈内存,这在内核中是非常有限的(我在Windows NT内核中尝试了C ++异常,并且抛出和展开异常占用了可用堆栈的一半-非常容易获得堆栈溢出并使整个系统崩溃)。

通常,您定义自己的newdelete运算符。我也发现了非常方便的a placement new和c ++ 11 rvalue引用。不能使用依赖异常的STL或其他C ++用户模式库。


0

当您尝试减少可执行内存时。通常在嵌入式(或移动)系统上完成。对于台式机应用程序,如果您希望为Web服务器或sql提供尽可能多的内存,则可以考虑在服务器上不需要它。

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.