为什么C ++中的new不会在失败时返回NULL

时间:2014-10-17 07:13:00

标签: c++

为什么new在失败时没有返回NULL?为什么它只在失败时抛出异常?

因为它在成功时返回指向对象的指针,为什么不在失败时呢?这种行为有什么具体原因吗?

8 个答案:

答案 0 :(得分:51)

在C ++中引入异常之前:

  • 失败的new - 表达式产生了一个空指针。

  • 在失败的构造函数中,为this指定了一个空指针。

引入例外后:

  • 失败的普通new - 表达式会引发异常。

  • 在失败的构造函数中,会抛出异常。

一个区别是,构造函数的失败报告现在也适用于创建没有动态分配的对象。

另一个区别是使用new表达式的代码现在可以更简单,因为错误处理可以从每个这样的代码位置移出并集中。

旧的nullpointer-result行为仍可通过std::nothrow获得(包括<new>标头)。这意味着使用new检查每个地方。因此,nullpointer结果与DRY原则相冲突,不重复(冗余提供了无穷无尽的机会来引入错误)。

答案 1 :(得分:27)

这是设计的。在C ++中,默认情况下抛出异常会通知各种类型的故障 - 但是,流是异常,抛出异常(双关语)默认

您可以使用nothrow版本:

T *p = new (std::nothrow) T(args);

if ( p == nullptr ) //must check for nullity
{
     std::cout << "memory allocation failed" << std::endl;
}

答案 2 :(得分:14)

只是一个历史记录,而不是这个问题的答案(有很多好的答案)......很久很久以前,当恐龙在地球上漫游而Turbo C ++统治着C ++程序员的世界时,{{1}实际上返回了new。引用当时的一本书(0bj3ct-0r13nt3d Pr0gr4mm1ng与ANSI和Turbo C ++ - 标题故意混淆,以便没有人误读它)第115页。

  

如果new运算符无法分配内存,则返回NULL,可用于检测新运算符的失败或成功。

因为这个遗留代码已经完成了大量的NULL检查......真正的噩梦让他们达到当前的标准......

但是,这是C ++标准5.3.4 / 13中的一小部分:

  

[注意:除非使用非抛出异常规范(15.4)声明分配函数,否则表示   无法通过抛出std :: bad_alloc异常来分配存储(第15,18.6.2.1节);它返回一个   否则为非null指针。如果使用非抛出异常规范声明分配函数,   它返回null表示无法分配存储,否则返回非空指针。 - 注意]如果   分配函数返回null,不进行初始化,不调用deallocation函数,   并且new-expression的值应为null。

告诉您,在某些特殊情况下,NULL可以返回new

答案 3 :(得分:8)

在C ++中,运算符new不仅要分配一块内存,还要进行类构造。因此,在语义上,运算符new比函数malloc更丰富。通过例外,我们可以获得更好的信息,我们可以更好地处理施工失败。

答案 4 :(得分:6)

在评论中,您强调您想知道为什么 new是这样设计的。在我看来,这都是关于对象组合。

考虑一个类Foo,其中包含(除其他外)std :: vector。

class Foo {
  public:
    explicit Foo(std::size_t n) : m_vec(n, 'A') {}
    ...
  private:
    std::vector<char> m_vec;
    ...
};

在动态内存中构造Foo时,有两个内存分配:一个是Foo本身,另一个是其向量的内容。如果其中任何一个失败,您需要确保没有泄漏,并且问题会报告给调用者。

Foo * pfoo = new Foo(desired_size);

假设new在失败时返回空指针。如果Foo的分配失败,pfoo将被设置为空指针,您可以依靠它来进行错误检测。了不起。然后你有一些错误处理代码,如:

if (pfoo == nullptr) { ... }

现在考虑嵌套对象。如果m_vec内容的分配失败,则必须检测到并将其报告给调用代码,但是没有办法让空指针传播到{{1 }}。唯一的方法是让std :: vector抛出异常(对于任何类型的构造问题),所以我们刚刚添加的错误处理代码将是无用的,因为它正在寻找空指针例外。

pfoo抛出new允许您处理嵌套动态内存分配失败,就像处理外部错误一样。这是避免代码重复和错误的有效方法。

C ++委员会可以让std::bad_alloc在分配失败时返回空指针,但是每个使用new作为内部存储器的构造函数都必须检测失败并将其转换为异常。因此,默认情况下抛出new会简化每个人的代码。

对于构造函数即使在分配失败时也可以做一些合理的事情,你可以明确地询问带有new的空指针,然后处理错误(或者你可以捕获{{1 }})。

除了

还要考虑如果我们堆栈分配一个Foo会发生什么。

std::nothrow

如果我们以某种方式设法使构造问题返回空指针,那么调用代码将如何检测它?

答案 5 :(得分:4)

在C中,所有动态内存请求都是通过函数调用完成的;代码检查任何分配请求以查看它们是否返回null是必要的,但是有一个明确定义的地方可以进行这样的检查(即在呼叫之后)。在C ++中,虽然代码可以通过调用new来构造对象,但大多数对象都是通过创建一个对象来创建的。

假设:

class Story
{
  Potter harry;
  Weasley ron,ginny,george,fred;
  Grainger hermione;
  Longbottom neville;
  Creevy colin;
  Story()
  {
  }
}

如果harry的构造函数试图创建一个new对象并失败,那么阻止系统无用地为所有其他字段创建对象的唯一方法就是让构造函数抛出一个例外。即使harry的构造函数失败,其他构造函数也可能成功创建这些对象,如果它们需要的内存少于Potter对象;因为创建harry失败会导致Story对象失效,但是,为剩余字段创建对象所花费的任何努力不仅会被浪费,而且需要系统花费更多的精力来摧毁那些无用的创建对象。

因为在大多数情况下,当new失败时系统唯一可以做的就是抛出异常,让new抛出异常本身更容易,并且如果代码捕获异常则更容易必要的,而不是要求所有调用new的代码必须检查它是否成功,如果没有则抛出异常。

答案 6 :(得分:2)

您可以指定您希望new返回0而不是使用std::bad_alloc

投掷std::nothrow parameter:
SomeType *p = new(std::nothrow) SomeType;

答案 7 :(得分:2)

默认情况下,当使用new运算符尝试分配内存且处理函数无法执行此操作时,将引发bad_alloc异常。但是当nothrow用作new的参数时,它会返回一个空指针。

nothrow常量是nothrow_t类型的值,其唯一目的是触发带有此类型参数的函数operator new(或operator new [])的重载版本。该值本身未被使用,但该版本的operator new应在失败时返回空指针,而不是抛出异常。