nothrow 还是异常?
Posted
技术标签:
【中文标题】nothrow 还是异常?【英文标题】:nothrow or exception? 【发布时间】:2011-06-02 01:58:24 【问题描述】:我是一名学生,我对 C++ 的了解很少,我会尝试扩展这些知识。这更像是一个哲学问题..我不是想实现什么。
自从
#include <new>
//...
T * t = new (std::nothrow) T();
if(t)
//...
//...
将隐藏异常,并且由于处理异常比简单的if(t)
更重,考虑到我们将不得不使用try-catch()
来检查一个简单的分配成功了(如果我们不这样做,就看着程序死掉)??
与使用nothrow new
相比,普通new
分配有哪些好处(如果有)?在这种情况下异常的开销是微不足道的?
另外,假设分配失败(例如,系统中不存在内存)。在那种情况下程序有什么可以做的,或者只是优雅地失败。没有办法在堆上找到空闲内存,当所有的都被保留时,有吗?
如果分配失败,并且std::bad_alloc
是throw
n,我们怎么能假设由于没有足够的内存来分配对象(例如new int
),会有足够的内存来存储例外??
感谢您的宝贵时间。我希望这个问题符合规则。
【问题讨论】:
如果上面的代码中的 new 失败。你打算在 if 语句中做什么?目前无法修复该错误。 @Martin,没什么。我只是对这种情况感到好奇,以及使用nothrow
是否有任何优势。其实答案让很多事情都清楚了。
你在内存分配中选择了一个不幸的例子。在现代桌面操作系统上运行的应用程序通常不会在内存不足时引发异常或返回错误消息。取而代之的是,整个系统只是冻结了,而操作系统则在打一场失败的战斗,即使用较慢的存储“模拟”请求的内存。但是如果应用于文件 I/O、网络访问、字符串解析或任何数量的其他任务,异常与返回码的问题是很好的。
【参考方案1】:
Nothrow 被添加到 C++ 中主要是为了支持想要编写无异常代码的嵌入式系统开发人员。如果您确实想在本地处理内存错误作为比 malloc() 后跟放置 new 更好的解决方案,它也很有用。最后,对于那些希望继续使用(当时是当前的)基于检查 NULL 的 C++ 编程风格的人来说,这是必不可少的。 [我自己提出了这个解决方案,这是我提出的少数几个没有被否决的事情之一:]
仅供参考:在内存不足时抛出异常对设计非常敏感且难以实现,因为例如,如果您要抛出一个字符串,您可能会出现双重错误,因为该字符串会进行堆分配。确实,如果由于堆崩溃到堆栈中而导致内存不足,您甚至可能无法创建临时的!这个特殊情况解释了为什么标准例外受到相当的限制。此外,如果您在本地捕获这样的异常,为什么应该通过引用而不是通过值来捕获(以避免可能的副本导致双重错误)。
正因为如此,nothrow 才能为关键应用程序提供更安全的解决方案。
【讨论】:
【参考方案2】:自从 处理异常比较重 与简单的 if(t) 相比,为什么不 正常的 new T() 不考虑较少 好的做法,考虑到我们会 必须使用 try-catch() 来检查是否 简单分配成功(如果我们 不要,只是看着程序死)?? 有什么好处(如果有的话) 与正常的新分配相比 使用一个新的?例外的 在这种情况下,开销是微不足道的 ?
使用异常的惩罚确实很重,但是(在经过适当调整的实现中)只有在抛出异常时才会支付惩罚 - 所以主线案例保持非常快,并且有在您的示例中,两者之间不太可能有任何可衡量的性能。
异常的优点是您的代码更简单:如果分配多个对象,您不必执行“分配 A;如果 (A) 分配 B;如果 (B) 等...”。清理和终止 - 在异常和主线情况下 - 最好由 RAII 自动处理(而如果您手动检查,您还必须手动释放,这很容易泄漏内存)。
另外,假设分配失败 (例如,系统中不存在内存)。 这个程序有什么可以做的吗 在那种情况下,或者只是失败 优雅地。没有办法找到 堆上的空闲内存,当一切都完成时 保留,有吗?
它可以做很多事情,而最好的事情将取决于正在编写的程序。失败和退出(优雅地或以其他方式)当然是一种选择。另一种是提前预留足够的内存,以便程序可以继续执行其功能(可能会降低功能或性能)。它可能能够释放一些自己的内存(例如,如果它维护可以在需要时重建的缓存)。或者(在服务器进程的情况下),服务器可能拒绝处理当前请求(或拒绝接受新连接),但保持运行,以便客户端不会断开连接,一旦内存可以重新开始工作返回。或者在交互式/GUI 应用程序的情况下,它可能会向用户显示错误并继续(允许他们修复内存问题并重试 - 或至少保存他们的工作!)。
如果分配失败,并且 std::bad_alloc 被抛出,我们怎么能 假设因为没有足够的 分配对象的内存(例如 new int),就会有足够的内存 存储异常??
不,通常标准库会通过提前分配少量内存来确保有足够的内存,以便在内存耗尽时引发异常。
【讨论】:
异常的代价比正常的代码流要多,但沉重是一个加载词。我敢打赌,在十个函数调用中引发异常的成本与通过十层函数调用将错误代码传递到可以处理的地方相同。然而代码更加直观和简洁(当使用异常时) @Martin York:你说得对,“重”确实是一个加载词 :) 但是在处理这样的一般问题时很难更具体 - 确切的惩罚将取决于实施,平台,以及捕获并重新抛出异常的那十个函数调用的数量。你可能会打赌成本是一样的,你可能是对的;如果我在我足够关心差异的情况下,我会测量它:-) @Martin:例外的代价要高得多。如果与异常相比,检查十个返回值甚至更明显,我会感到惊讶。它在比异常更糟糕的 100,000 次成功操作期间检查这十个返回值。因此,对于用户提供的数据的验证,返回值是首选,因为失败相对频繁。网络操作再次相对频繁地失败,因此使用返回值。分配,永远不会失败***,所以例外。 [***脚注:大多数系统会在地址空间耗尽之前页面死机] @Ben Voigt:是的,你是对的,图片更复杂 :) 我不确定你是否可以 100% 禁用交换(在删除文件的只读页面的意义上- 映射来自 RAM 的可执行文件),但结合应用程序级别(例如mprotectall()
)和系统级别(例如 Linux 上的 /proc/sys/vm/swappiness
)调整,即使在低内存中也可以实现保持应用程序响应的目标条件,代价是一旦记忆消失就会碰壁。但我同意这是例外(请原谅这个表达!)而不是规则。
(当然在 Linux 的情况下,默认情况下 malloc() 和它的同类函数永远不会失败 - 相反,内存被延迟分配,当它被访问时,如果在 that 时间发现不够用,然后内核选择一个进程杀死以释放一些...)【参考方案3】:
预计内存不足是一种罕见的事件,因此在发生异常时引发异常的开销不是问题。实现可以“预分配”抛出std::bad_alloc
所需的任何内存,以确保即使在程序内存不足时也可用。
默认情况下抛出异常而不是返回 null 的原因是它避免了每次分配后都需要进行 null 检查。许多程序员不会这样做,如果程序在分配失败后继续使用空指针,它可能会在稍后崩溃并出现类似分段错误的情况,这并不能说明问题的真正原因。使用异常意味着如果不处理 OOM 条件,程序将立即终止并返回一个错误,该错误实际上表明出了什么问题,这使得调试更加容易。
如果它们抛出异常,编写处理内存不足情况的代码也更容易:不必单独检查每个分配的结果,您可以将catch
块放在调用堆栈的某个较高位置以捕获整个程序中许多地方的 OOM 条件。
【讨论】:
【参考方案4】:在 Symbian C++ 中,它以相反的方式工作。如果你想在OOM时抛出异常,你必须这样做
T* t = new(ELeave) T();
当 OOM 很奇怪时,您对抛出新异常的逻辑是正确的。一个可以管理的场景突然变成了程序终止。
【讨论】:
这只能说明 Symbian C++ 实际上并不是标准的 C++。现在,争论错误代码而不是异常已经很老了,并且一再被证明是错误的。可以在此处找到简明摘要:boost.org/community/exception_safety.html 错了?大声笑,这就像在争论变速杆汽车变速器是错误的【参考方案5】:我认为为什么使用常规 new
而不是 nothrow
new 背后的基本原理与为什么通常首选异常而不是显式检查每个函数的返回值的原因有关。如果找不到内存,并不是每个需要分配内存的函数都一定知道该怎么做。例如,一个将内存作为子程序分配给某个算法的深度嵌套函数可能不知道如果找不到内存,应该采取什么正确的行动。使用引发异常的new
版本允许调用子例程的代码(而不是子例程本身)采取更适当的操作过程。这可能很简单,就像什么都不做,看着程序死掉(如果你正在编写一个小玩具程序,这完全没问题),或者发出一些更高级别的程序结构开始丢弃内存的信号。
关于你问题的后半部分,如果你的程序内存不足,你实际上可以做一些事情来使内存更可用。例如,您的程序可能有一部分缓存旧数据,并且可以告诉缓存在资源紧张时立即驱逐所有内容。您可能会将一些不太重要的数据分页到磁盘,这可能比您的内存有更多的空间。有很多这样的技巧,通过使用异常,可以将所有紧急逻辑放在程序的顶部,然后让程序的每个执行分配的部分都不会捕获 bad_alloc 而是让它传播到顶部。
最后,即使内存不足,通常也可以抛出异常。许多 C++ 实现会在堆栈(或其他一些非堆内存段)中为异常保留一些空间,因此即使堆空间不足,也可以为异常找到内存。
希望这会有所帮助!
【讨论】:
【参考方案6】:因为“太昂贵”而绕过异常是过早的优化。如果没有抛出异常,则几乎没有 try/catch 的开销。
这个程序有什么可以做的吗 在那种情况下
通常不会。如果系统中没有内存,您甚至可能无法将任何内容写入日志,或打印到标准输出或任何内容。如果你内存不足,那你就完蛋了。
【讨论】:
“过早的优化”论点是上个世纪的口号,它甚至在有机会之前就扼杀了任何合理的讨论。例如,在稳定性是关键的时间关键型环境中,您真的不希望一堆未知的异常处理破坏您的软件流程。 @StarShine:这是一个不错的论点。但一般情况下“太贵”的例外情况不是您应该担心的。 我曾经被教导同意你的说法,但是怎么想 1)“一般情况”越来越不保证使用 C++ 和 2)什么是“例外”的语义' 往往会根据您的里程/编程语言而有所不同。我的意思是,原理很好,如果每个人都理解相同的东西,它可以节省开发时间。在实践中..以上是关于nothrow 还是异常?的主要内容,如果未能解决你的问题,请参考以下文章
看起来 is_nothrow_constructible_v() 在 MSVC 中被破坏了,我错了吗?
早期malloc分配时,如果内存耗尽分配不出来,会直接返回NULL。现在分配不出来,直接抛出异常(可使用nothrow关键字)