没有或例外?

时间:2010-12-31 18:23:14

标签: c++ out-of-memory dynamic-memory-allocation new-operator

我是一名学生,我对C ++知之甚少,我试图扩展它。这更像是一个哲学问题。我不是想要实现某些东西。

由于

#include <new> 
//...
T * t = new (std::nothrow) T();
if(t)
{
    //...
}
//...

隐藏异常,并且由于与简单的if(t)相比,处理异常更重,为什么不正常的new T()不被视为不太好的做法,考虑到我们将不得不使用{{ 1}}检查一个简单的分配是否成功(如果我们不成功,只看程序死掉)?

与使用try-catch()相比,正常new分配有哪些好处(如果有的话)?在这种情况下,例外的开销是微不足道的?

此外,假设分配失败(例如,系统中不存在内存)。程序在那种情况下可以做什么,或者只是优雅地失败。当保留所有内存时,没有办法在堆上找到空闲内存吗?

如果分配失败,nothrow newstd::bad_alloc n,我们怎么能假设因为没有足够的内存来分配对象(例如a throw),那么将有足够的内存来存储异常??

感谢您的时间。我希望这个问题符合规则。

6 个答案:

答案 0 :(得分:8)

  

由于   处理例外更重要   与简单的if(t)相比,为什么不是   正常的新T()不算少   好的做法,考虑到我们会   必须使用try-catch()检查是否   简单分配成功(如果我们   不要,只看程序死)??   有什么好处(如果有的话)   正常的新配置相比   使用nothrow new?异常的   在这种情况下,开销微不足道   ?

使用异常的代价确实非常沉重,但是(在一个经过适当调整的实现中)只会在抛出异常时支付 - 因此主线情况保持非常快,并且在你的例子中,两者之间不可能有任何可衡量的表现。

异常的优点是代码更简单:如果分配多个对象,则不必执行“分配A; if(A){分配B; if(B)等...”。在例外和主线情况下的清理和终止最好由RAII自动处理(如果您手动检查,则还必须手动释放,这使得泄漏内存变得非常容易)。

  

另外,假设分配失败   (例如,系统中不存在内存)。   程序可以做什么   在那种情况下,或只是失败   优雅。没有办法找到   堆上的空闲内存,一切都是   保留,有吗?

它可以做很多事情,最好的办法取决于正在编写的程序。失败和退出(优雅或其他)当然是一种选择。另一种方法是提前预留足够的内存,以便程序可以继续其功能(可能具有降低的功能或性能)。它可以释放一些自己的内存(例如,如果它维护可以在需要时重建的缓存)。或者(在服务器进程的情况下),服务器可能拒绝处理当前请求(或拒绝接受新连接),但保持运行,以便客户端不会丢弃其连接,并且一旦内存可以再次开始工作回报。或者在交互式/ GUI应用程序的情况下,它可能会向用户显示错误并继续(允许他们修复内存问题并重试 - 或者至少保存他们的工作!)。

  

分配失败,并且   抛出std :: bad_alloc,我们怎么办?   假设因为没有足够的   用于分配对象的内存(例如a   新的int),会有足够的内存   存储异常??

不,通常标准库通常会事先通过分配少量内存来确保在内存耗尽时会有足够的内存来引发异常。

答案 1 :(得分:6)

Nothrow被添加到C ++主要是为了支持想要编写无异常代码的嵌入式系统开发人员。如果您实际上想要在本地处理内存错误作为比malloc()后面的placement new更好的解决方案,这也很有用。最后,对于那些希望继续使用(当时是最新的)基于检查NULL的C ++编程样式的人来说,这是必不可少的。 [我自己提出了这个解决方案,我提出的少数几个没有被投票的事情之一:]

仅供参考:在内存不足时抛出异常非常敏感且难以实现,因为例如,如果您要抛出一个字符串,则可能会因为字符串进行堆分配而导致错误。实际上,如果由于堆崩溃而导致内存不足,您甚至可能无法创建临时堆!这个特殊情况解释了为什么标准异常受到相当限制。此外,如果你在本地公平地捕获这样的异常,为什么你应该通过引用而不是值来捕获(以避免可能的副本导致双重错误)。

由于这一切,nothrow为关键应用程序提供了更安全的解决方案。

答案 2 :(得分:4)

我认为,为什么使用常规new而不是nothrow new的原因与通常优先选择异常以显式检查每个函数的返回值的原因有关。并非每个需要分配内存的功能都必须知道如果找不到内存该怎么办。例如,一个深度嵌套的函数将内存作为子程序分配给某些算法可能不知道如果找不到内存,采取的正确行动方式是什么。使用抛出异常的new版本允许调用子例程的代码而不是子例程本身采取更合适的操作。这可能就像什么都不做,看着程序死掉一样简单(如果你正在编写一个小玩具程序,那就完全没问题了),或者发出一些更高级别的程序结构信号来开始丢弃内存。

关于你问题的后半部分,如果你的程序内存不足会使内存更加可用,那么你可以做的事情实际上可以做。例如,您可能有一部分程序缓存旧数据,并且可以告诉缓存在资源紧张时立即驱逐所有内容。您可能会将一些不太重要的数据写入磁盘,这可能比您的内存有更多的空间。有很多这样的技巧,并且通过使用异常,可以将所有紧急逻辑放在程序的顶部,然后让程序的每个部分执行分配都不会捕获bad_alloc而是让它传播到顶部。

最后,即使内存稀少,通常也可以抛出异常。许多C ++实现为异常保留了堆栈(或其他一些非堆内存段)中的一些空间,因此即使堆空间不足,也可以为异常找到内存。

希望这有帮助!

答案 3 :(得分:3)

绕过例外,因为它们“过于昂贵”是过早的优化。如果没有抛出异常,则几乎没有try / catch的开销。

  

程序可以做什么   在那种情况下

通常不会。如果系统中没有内存,您可能甚至无法将任何内容写入日志,或打印到标准输出或其他任何内容。如果你的记忆力不足,那你几乎已经搞砸了。

答案 4 :(得分:2)

内存耗尽预计是一种罕见的事件,因此在发生异常时引发异常的开销不是问题。实现可以“预先分配”抛出std::bad_alloc所需的任何内存,以确保即使程序内存不足也可以使用它。

默认情况下抛出异常而不是返回null的原因是它避免了在每次分配后需要进行空检查。许多程序员都不会这么做,如果程序在分配失败后继续使用空指针,它可能会稍后崩溃,例如分段错误,这并不表示问题的真正原因。使用异常意味着如果不处理OOM条件,程序将立即终止,并显示实际指示出错的错误,这使得调试变得更加容易。

如果它们抛出异常,为内存不足的情况编写处理代码也更容易:不必单独检查每个分配的结果,你可以在调用堆栈的某个位置放置一个catch块从整个计划的许多地方捕捉OOM条件。

答案 5 :(得分:-1)

在Symbian C ++中,它以相反的方式工作。如果你想要在OOM时抛出异常

T* t = new(ELeave) T();

当OOM变得奇怪时,你对于抛出新异常的逻辑是正确的。可管理的场景突然变成程序终止。