为什么 C++ 中的新功能在失败时不返回 NULL。

Why doesn't new in C++ return NULL on failure

本文关键字:返回 NULL 失败 C++ 新功能 为什么      更新时间:2023-10-16

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

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

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

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

  • 在一个失败的构造函数中,为this分配了一个null指针。

引入例外情况后:

  • 一个失败的普通new表达式引发异常。

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

一个不同之处在于,构造函数的故障报告现在也适用于创建没有动态分配的对象。

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

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

它是经过设计的。在C++中,默认情况下,通过抛出异常来通知每种故障—然而,流是异常,默认情况下,不会抛出异常(双关语)

您可以将另一个版本用作:

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

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

如果新运算符未能分配内存,则返回NULL,该值可用于检测新运算符的失败或成功。

因为这个遗留代码充满了大量的NULL检查。。。让他们达到目前的标准真是一场噩梦。。。

然而,这里是C++标准5.3.4/13:的一小部分

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

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

,运算符new不仅是分配一块内存,也是类构造。因此,在语义上,运算符new比函数malloc丰富得多。通过例外情况,我们可以获得更好的信息,我们可以更好地处理施工故障。

在评论中,您强调您想知道为什么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内容的分配失败,您必须检测到这一点并将其报告给调用代码,但您无法获得一个空指针来传播到pfoo的分配。唯一的方法是让std::vector抛出一个异常(对于任何类型的构造问题),所以我们刚刚添加的错误处理代码将毫无用处,因为它在寻找空指针而不是异常。

new抛出std::bad_alloc可以像处理外部内存分配失败一样处理嵌套的动态内存分配失败。这是一种避免代码重复和错误的强大方法。

C++委员会本可以让new在分配失败时返回一个空指针,但每个将new用于内部内存的构造函数都必须检测到失败并将其变成异常。因此,默认情况下让new抛出可以简化每个人的代码。

对于那些即使在分配失败的情况下,构造函数也可以做一些合理的事情的情况,您可以显式地使用std::nothrow请求空指针,然后处理错误(或者您可以捕获std::bad_alloc)。

旁白

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

Foo foo(desired_size, 'B');

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

在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的代码都必须检查它是否成功并在不成功的情况下抛出异常更容易。

您可以指定您希望new返回0,而不是使用std::nothrow parameter: 抛出std::bad_alloc

SomeType *p = new(std::nothrow) SomeType;

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

nothrow常量是nothrow_t类型的值,其唯一目的是触发函数运算符new(或运算符new[])的重载版本,该重载版本接受此类型的参数。不使用值本身,但该版本的运算符new应在失败时返回空指针,而不是引发异常。