Подтвердить что ты не робот

Почему новый С++ не возвращает NULL при сбое

Почему new не возвращает NULL при ошибке? Почему он генерирует исключение только при ошибке?

Как он возвращает указатель на объект на успех, почему бы не при ошибке? Есть ли какая-то конкретная причина такого поведения?

4b9b3361

Ответ 1

До того, как исключения были введены в С++:

  • Ошибка new -выражения вызвала нулевой указатель.

  • В отказоустойчивом конструкторе присваивается нулевой указатель на this.

После введения исключений:

  • Неверное обычное new -выражение вызывает исключение.

  • В отказоустойчивом конструкторе выдается исключение.

Одно отличие состоит в том, что отчеты об ошибках для конструкторов теперь также работают и для создания объектов без динамического выделения.

Другое отличие состоит в том, что код с использованием new -выражений теперь может быть проще, поскольку обработка ошибок может быть перемещена из каждого такого места кода и централизована.

Старое поведение результата nullpointer-result по-прежнему доступно через std::nothrow (включая заголовок <new>). Это подразумевает проверку в каждом месте с помощью new. Таким образом, результаты nullpointer противоречат принципу DRY, не повторяйте себя (избыточность предлагает бесконечный поток возможностей для внесения ошибок).

Ответ 2

Это по дизайну. В С++ каждый вид сбоя уведомляется путем исключения исключения по умолчанию — потоки, однако, являются исключением, которое по умолчанию выполняет не исключение (каламбур).

Вы можете использовать версию nothrow как:

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

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

Ответ 3

Просто историческая заметка, а не ответ на этот вопрос (есть много хороших ответов)... давным-давно, когда динозавры бродили по земле, а Turbo С++ управлял миром программистов на C++, new на самом деле вернулся NULL. Цитата из книги тех дней (0bj3ct-0r13nt3d Pr0gr4mm1ng с ANSI и Turbo С++ - название намеренно запутано, чтобы никто по ошибке больше не читал) стр. 115.

Если новый оператор не может выделить память, он возвращает NULL, который может использоваться для обнаружения отказа или успеха нового оператора.

потому что этот устаревший код заполнен множеством NULL-проверок... настоящим кошмаром, чтобы довести их до текущего стандарта...

Однако, вот крошечный кусок стандарта С++ 5.3.4/13:

[Примечание: если функция распределения не объявлена ​​с помощью спецификации исключения исключений (15.4), это указывает неспособность выделить хранилище путем выброса исключения std:: bad_alloc (пункт 15, 18.6.2.1); он возвращает ненулевой указатель в противном случае. Если функция распределения объявлена ​​с не-бросающей спецификацией исключения, он возвращает значение NULL, чтобы указать отказ в распределении хранилища и ненулевой указатель в противном случае. -end note] Если функция распределения возвращает null, инициализация не выполняется, функция освобождения не должна вызываться, и значение нового выражения должно быть нулевым.

в котором говорится, что в некоторых особых случаях new может возвращать NULL

Ответ 4

в С++ оператор new не только выделяет кусок памяти, но и класс. таким образом, в семантике оператор new намного богаче функции malloc. за исключениями, мы получаем более полную информацию, и мы можем справиться с ошибками строительства лучше.

Ответ 5

В комментариях вы подчеркнули, что хотите знать, почему new разработан таким образом. На мой взгляд, все это касается композиции объекта.

Рассмотрим класс Foo, содержащий (среди прочего) a 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 throw a std::bad_alloc, вы можете рассматривать отказы распределения распределенных динамических папок так же, как и внешние. Это мощный способ избежать дублирования кода и ошибок.

Комитет С++ мог бы позволить new вернуть нулевой указатель на отказ распределения, но каждый конструктор, который использовал new для внутренней памяти, должен был бы обнаружить сбой и превратить его в исключение. Таким образом, при использовании new throw по умолчанию упрощается каждый код.

В тех случаях, когда ваш конструктор может сделать что-то разумное даже перед лицом сбоя распределения, вы можете явно запросить нулевой указатель с помощью std::nothrow, а затем обработать ошибку (или вы можете поймать std::bad_alloc).

Помимо

Также рассмотрим, что произойдет, если мы будем складывать выделение Foo.

Foo foo(desired_size, 'B');

Если бы нам удалось каким-то образом сделать проблемы с конструкцией, верните нулевой указатель, как бы код вызова обнаружил его?

Ответ 6

В C все запросы для динамической памяти выполняются вызовами функций; необходимо, чтобы код проверял любые запросы на распределение, чтобы увидеть, возвращают ли они null, но есть четко определенное место, где такие проверки могут быть сделаны (т.е. сразу после вызова). В С++, хотя код может создавать объекты, вызывая 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, должен проверить, удалось ли это сделать, и выбросить исключение, если оно не было.

Ответ 7

вы можете указать, что вы хотите, чтобы new возвращал 0 вместо того, чтобы бросать std::bad_alloc с помощью std::nothrow parameter:

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

Ответ 8

По умолчанию, когда новый оператор используется для попытки выделения памяти, и функция обработки не может этого сделать, генерируется исключение bad_alloc. Но когда nothrow используется как аргумент для new, он вместо этого возвращает нулевой указатель.

Константа nothrow - это значение типа nothrow_t с единственной целью запуска перегруженной версии функционального оператора new (или оператора new []), которое принимает аргумент этого типа. Само это значение не используется, но эта версия оператора new должна возвращать нулевой указатель в случае сбоя, а не для исключения исключения.