【问题标题】:New (std::nothrow) vs. New within a try/catch block新 (std::nothrow) 与 try/catch 块中的新
【发布时间】:2011-09-01 23:09:37
【问题描述】:

我在学习new 之后做了一些研究,与我习惯的malloc() 不同,它不会为失败的分配返回 NULL,并且发现有两种不同的方法来检查 new 是否成功。这两种方式是:

try
{
    ptr = new int[1024];
}
catch(std::bad_alloc& exc)
{
    assert();
};

ptr = new (std::nothrow) int[1024];
if(ptr == NULL) 
    assert();

我相信这两种方法可以实现相同的目标,(当然,如果我错了,请纠正我!),所以我的问题是:

这是检查new 是否成功的更好选择,完全基于可读性、可维护性和性能,同时忽略事实上的 c++ 编程约定。

【问题讨论】:

  • 使用例外。它们使您的代码更有意义、更本地化、可维护和可扩展。
  • 只看可读性,绝对是没有异常处理的。关于性能,也许还有nothrow。但我敢肯定,过度例外论者会争辩。但是如果你只是想要一个简单的断言失败,你也可以使用 throwing 变体并省略异常处理程序;)
  • 这段代码有什么意义?您正在捕获一个特定的、有意义的异常,然后通过assert() 创建一个无意义的异常...
  • 我不知道,不好的例子。将 malloc() 用于 C 数组。试图处理 std::bad_alloc() 几乎总是一个错误。它实际上是一个异步异常。当您开始消耗超过一半的地址空间时,是时候考虑切换到 64 位操作系统了。
  • 这只是重新散列返回代码与错误报告的异常。

标签: c++ performance exception-handling readability


【解决方案1】:

考虑一下你在做什么。你正在分配内存。如果由于某种原因内存分配无法正常工作,你assert。如果您让std::bad_alloc 传播回main,这或多或少会发生什么。在发布版本中,assert 是无操作的,您的程序在尝试访问内存时会崩溃。所以这和让异常冒泡一样:停止应用程序。

所以问自己一个问题:你真的需要关心如果你的内存用完会发生什么吗?如果你所做的只是断言,那么异常方法会更好,因为它不会用随机的asserts 混淆你的代码。您只需让异常回退到main

如果您在无法分配内存的情况下确实有一个特殊的代码路径(也就是说,您实际上可以继续运行),那么异常可能是也可能不是,这取决于代码路径是什么。如果代码路径只是一个通过将指针设为空来设置的开关,那么nothrow 版本会更简单。如果相反,您需要做一些完全不同的事情(从静态缓冲区中拉出,或删除一些东西,或者其他什么),那么捕获 std::bad_alloc 是非常好的。

【讨论】:

  • 这是一个非常有说服力的论点,我不知道异常可能会爬上调用堆栈以找到一个 catch 块。 ...我不知道异常可以做的很多事情。这么多年了,也许是我终于学会它的时候了,啊哈
【解决方案2】:

这取决于分配发生位置的上下文。如果即使分配失败您的程序也可以继续(可能会向调用者返回错误代码),则使用std::nothrow 方法并检查NULL。否则你会使用异常来控制流,这不是一个好习惯。

另一方面,如果您的程序绝对需要成功分配内存才能运行,请使用try-catch 捕获(不一定在new 附近)异常并退出优雅地从程序中。

【讨论】:

  • 我没有考虑到这一点,我想在这种情况下混合和匹配不会是糟糕的风格。显然,出于这个原因,存在两种方式。谢谢!
  • 这是一个很好的答案。它总结了异常的整个哲学:在异常情况下使用异常,并在可以有意义地处理它们的地方捕获它们。另一方面,对于您可以并且想要在本地处理的非异常运行时情况,请不要使用异常。
【解决方案3】:

从纯粹的性能角度来看,这无关紧要。异常处理存在固有的开销,尽管这种开销通常值得在应用程序的可读性和维护方面进行权衡。这种性质的内存分配失败不应该出现在 99% 的应用程序中,因此这种情况应该很少发生。

从性能的角度来看,您通常希望避免使用标准分配器,因为它的性能相对较差。

综上所述,我通常接受抛出异常的版本,因为通常我们的应用程序处于这样一种状态,即如果内存分配失败,除了优雅地退出并显示适当的错误消息外,我们几乎无能为力,而且我们通过不保存性能来节省性能需要 NULL 检查我们新分配的资源,因为根据定义,分配失败会将范围从重要的地方移出。

【讨论】:

  • 所以假设没有任何问题,异常的性能损失会小于指针的相等性检查?如果是这样的话,我认为现在没有理由不使用异常。 (是的,我尝试将所有分配需求包装到一个小对象分配器中,将所有内存内容也放在一个整洁的地方)
  • 在使用异常(语言为你做的)时仍然有一些惩罚,例如适当地设置堆栈、SEH 处理等。一般我不认为会有可测量的差异在“没事”的情况下两者之间。在失败的情况下,异常代码将涉及更多,但它通过不需要NULL 检查来清理代码。到那时,这确实是个人喜好问题。
  • 在 GCC 实现中,如果不使用异常,则不会花费您任何费用(除了可能稍微差一点的内存局部性)——您只需在实际抛出异常时付费。跨度>
  • @Kerrek SB ,乍得 - 那没关系。我假设如果有任何性能问题,这将是我的错,而不是异常处理或其他任何事情。只要它不是真正明显的差异,那么对我来说唯一的因素就是可读性和可维护性。
【解决方案4】:

new 用于创建对象,而不是分配内存,因此您的示例有些人为。

对象构造函数通常会在失败时抛出。在 Visual Studio 中多次执行 new 实现后,我认为代码不会捕获任何异常。因此,在创建对象时寻找异常通常是有意义的。

认为 std::bad_alloc 仅在内存分配部分失败时才被抛出。我不确定如果你将std::nothrow 传递给new 会发生什么,但是对象构造函数会抛出 - 我读过的文档中存在歧义。

这两种方法之间的性能差异可能无关紧要,因为大部分处理器时间很容易花在对象构造函数或搜索堆上。

经验法则并不总是合适的。例如,实时系统通常会限制动态内存分配,因此new(如果存在)可能会过载。在这种情况下,它可能会使用返回的空指针并在本地处理故障。

【讨论】:

  • 如果你的主对象有子对象,那么如果它们分配内存,它们也会抛出 bad_alloc ......所以你不能确定它是否是主分配失败。但是,您不必在意,因为该对象无论如何都不可用,编译器将负责对部分构造的对象进行所需的清理。 我建议您先学习该语言,然后再给出具有误导性的任意答案。特别是,如果您建议在创建对象时查找异常,那么如果这意味着在每个分配中都使用 try/catch。
  • 在编写任何用于专业应用程序的代码之前,我建议您阅读 Exceptional C++ 和其他类似书籍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-21
  • 1970-01-01
相关资源
最近更新 更多