'New' и 'delete' устарели в C ++?

68

Я наткнулся на тест, который включал объявление массива с различными размерами. Первое, что пришло мне в голову, это то, что мне нужно будет использовать динамическое распределение с newкомандой, например так:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

Однако я увидел, что одно из решений допускает следующий случай:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

После небольшого исследования я прочитал, что g ++ позволяет это, но это заставило меня задуматься, в каких случаях тогда необходимо использовать динамическое распределение? Или компилятор переводит это как динамическое распределение?

Функция удаления включена. Обратите внимание, однако, что вопрос здесь не об утечках памяти.

learning_dude
источник
54
Во втором примере используется массив переменной длины, который никогда не был частью C ++. Для этого случая используйте std::vectorвместо ( std::vector<int> array(N);).
Какой-то программист чувак
7
Прямой ответ на ваш вопрос должен быть: нет, он не считается устаревшим. Несмотря на то, что современные версии C ++ предоставляют множество функций, упрощающих управление владением памятью (интеллектуальные указатели), по-прежнему распространена практика выделения объектов путем new OBJнепосредственного вызова .
pptaszni
8
Для других людей, которые не понимают, почему люди говорят об утечках памяти, вопрос был отредактирован, чтобы исправить ошибку, которая не была существенной для вопроса
Майк Карон
4
@Mannoj предпочитает использовать термины Dynamic и Automatic для кучи и суммирования. Это редко, но возможно реализовать C ++ без куч и стеков.
user4581301
1
Ничто никогда не осуждается в C ++ и никогда не будет. Это часть того, что означает C ++.
JoelFan

Ответы:

114

Ни один фрагмент кода, который вы показываете, не является идиоматическим, современным кодом C ++.

newи deletenew[]и delete[]) не устарели в C ++ и никогда не будут. Они все еще являются способом создания динамически размещаемых объектов. Однако, поскольку вы всегда должны сопоставлять a newс delete(и a new[]с a delete[]), лучше всего их хранить в (библиотечных) классах, которые гарантируют это для вас. См. Почему программисты на C ++ должны минимизировать использование 'new'? ,

Ваш первый фрагмент использует «голый», new[]а затем никогда не delete[]созданный массив. Это проблема. std::vectorделает все, что вам нужно здесь просто отлично. Он будет использовать некоторую скрытую форму new(я не буду вдаваться в детали реализации), но для всех, что вам нужно, это динамический массив, но лучше и безопаснее.

Ваш второй фрагмент использует «массивы переменной длины» (VLA), функцию C, которую некоторые компиляторы также допускают в C ++ в качестве расширения. В отличие от newVLA, по существу, расположены в стеке (очень ограниченный ресурс). Но что более важно, они не являются стандартной функцией C ++, и их следует избегать, потому что они не переносимы. Они, конечно, не заменяют динамическое (т.е. кучное) распределение.

Макс Лангоф
источник
3
Я хотел бы добавить, что хотя VLA официально не включены в стандарт, они поддерживаются всеми основными компиляторами, и поэтому решение о том, следует ли их избегать, является скорее вопросом стиля / предпочтения, чем реалистической заботой о переносимости.
Stack Tracer
4
Также помните, что вы не можете вернуть массив или сохранить его в другом месте, поэтому VLA никогда не переживет время выполнения функции
Руслан
16
@StackTracer Насколько мне известно, MSVC не поддерживает VLA. И MSVC, безусловно, является «основным компилятором».
Макс Лангоф
2
«Вы должны всегда сопоставлять новое с удалением» - не при работе с ним Qt, поскольку все его базовые классы имеют сборщики мусора, поэтому вы просто используете newи забываете об этом большую часть времени. Для элементов GUI, когда родительский виджет закрыт, дочерние элементы выходят из области видимости и автоматически собирают мусор.
VSZ
6
@vsz Даже в Qt у каждого по- newпрежнему есть соответствие delete; просто deletes выполняется родительским виджетом, а не в том же блоке кода, что и news.
Джрамси
22

Ну, для начала, new/ deleteне осуждается.

В вашем конкретном случае, они не единственное решение, хотя. То, что вы выберете, зависит от того, что скрыто под вашим комментарием "сделать что-то с массивом".

Ваш второй пример использует нестандартное расширение VLA, которое пытается разместить массив в стеке. Это имеет определенные ограничения, а именно: ограниченный размер и невозможность использования этой памяти после того, как массив выходит из области видимости. Вы не можете переместить его, он «исчезнет» после того, как стек раскрутится.

Поэтому, если ваша единственная цель - выполнить локальные вычисления, а затем выбросить данные, это может на самом деле работать нормально. Однако более надежным подходом было бы динамическое распределение памяти, предпочтительно с std::vector. Таким образом, вы получаете возможность создавать пространство для ровно столько элементов, сколько вам нужно, основываясь на значении времени выполнения (к чему мы стремимся все время), но оно также хорошо очищается и вы можете его убрать этой области, если вы хотите сохранить память на потом.

Возвращаясь к началу, возможно , vector будем использовать newнесколько уровней глубже, но вам не следует беспокоиться об этом, поскольку интерфейс, который он представляет, намного лучше. В этом смысле использование newи deleteможет считаться обескураженным.

Бартек Банахевич
источник
1
Обратите внимание на «... несколько слоев глубже». Если вы хотите реализовать свои собственные контейнеры, вам все равно следует избегать использования newи delete, а использовать интеллектуальные указатели, такие как std::unique_pointer.
Макс
1
который на самом деле называетсяstd::unique_ptr
user253751
2
Деструктор @Max: по std::unique_ptrумолчанию вызывает deleteили delete[], что означает, что принадлежащий объект должен был быть выделен newили в new[]любом случае, в какие вызовы были скрыты std::make_uniqueначиная с C ++ 14.
Лоран LA RIZZA
15

Ваши вторые примеры используют массивы переменной длины (VLA), которые фактически являются функцией C99 ( не C ++!), Но тем не менее поддерживаются g ++ .

Смотрите также этот ответ .

Обратите внимание, что массивы переменной длины отличаются от new/ deleteи никоим образом не "осуждают" их.

Также имейте в виду, что VLA не являются ISO C ++.

andreee
источник
13

Современный C ++ предоставляет более простые способы работы с динамическими распределениями. Умные указатели могут позаботиться об очистке после исключений (которые могут произойти где угодно, если это разрешено) и ранних возвратов, как только указанные структуры данных выйдут из области видимости, поэтому может иметь смысл использовать их вместо этого:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

С C ++ 14 вы также можете написать

auto buffer_new = std::make_unique<int[]>(size);

это выглядит еще лучше и предотвратит утечку памяти в случае сбоя выделения. С C ++ 20 вы должны быть в состоянии сделать столько, сколько

auto a = std::make_shared<int[]>(size);

это для меня все еще не компилируется во время написания с gcc 7.4.0. В этих двух примерах мы также используем autoвместо объявления типа слева. Во всех случаях используйте массив как обычно:

buffer_old[0] = buffer_new[0] = 17;

Утечки памяти newи сбои при дублировании delete- это то, что C ++ было разбито на протяжении многих лет, являясь «центральной точкой» аргументации для перехода на другие языки. Может быть, лучше избегать.

Аудрюс Мескаускас
источник
Вы должны избегать unique/shared_ptrконструкторов в пользу того make_unique/shared, что вы не только не должны писать составной тип дважды (используя auto), но и не рискуете потерять память или ресурсы, если построение завершится неудачей частично (если вы используете тип, который может дать сбой)
Симон Бьюкен
2
make_unique доступен с массивами из C ++ 14 и make_shared только из C ++ 20. Это по-прежнему редко используется по умолчанию, поэтому предложение std :: make_shared <int []> (size) выглядело для меня несколько раньше.
Аудрюс Мескаускас
Справедливо! Я не очень make_shared<int[]>много пользуюсь , когда ты всегда хочешь vector<int>, но приятно знать.
Симон Бьюкен
Чрезмерный педантизм, но IIRC unique_ptrконструктор nothrow, поэтому для Tкоторых есть nothrow конструкторов, так что нет никакого риска утечки с unique_ptr(new int[size])и shared_ptrимеет следующее: «Если исключение, удаление р называется , когда T не является тип массива, удаление [ ] p в противном случае. ", так что вы имеете тот же эффект - риск для unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Саймон Бьюкен
3

новые и удалить не становятся устаревшими.

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

new и delete являются основополагающими аспектами языка. Постоянство объекта может управляться с помощью new и delete. Это определенно не будет устаревшим.

Утверждение - int array [N] - это способ определения массива. Массив может использоваться в пределах объема кодового блока. Он не может быть передан подобно тому, как объект передается другой функции.

Gopinath
источник
2

Первый пример нуждается delete[]в конце, иначе у вас будет утечка памяти.

Во втором примере используется переменная длина массива, которая не поддерживается C ++; это позволяет только константное выражение для длины массива .

В этом случае это полезно использовать в std::vector<>качестве решения; это оборачивает все действия, которые вы можете выполнить над массивом, в класс шаблона.

зиг бритва
источник
3
Что вы имеете в виду с "до C ++ 11"? Я почти уверен, VLA никогда не становились частью стандарта.
Чарри
посмотрите на стандарт c ++ 14 [стандарт c ++ 14] ( isocpp.org/files/papers/N3690.pdf ) на стр. 184, параграф 8.3.4
зиг бритва
4
То не . Стандарт, но только проект и часть о «массивов время выполнения связаны» не превратить его в стандарт, насколько я могу сказать , cppreference не говоря уже о Власе там.
churill
1
@zigrazor cppreference.com содержит список ссылок на самые близкие проекты до / после публикации каждого из стандартов. Опубликованные стандарты не находятся в свободном доступе, но эти проекты должны быть очень близки. Как вы можете видеть из номеров документов, ваш связанный проект представляет собой более старый рабочий проект для C ++ 14.
грецкий орех
2
@learning_dude Не поддерживается стандартами. Ответ (сейчас) правильный (пусть и короткий). Это работает только для вас, потому что GCC допускает это как нестандартное расширение.
грецкий орех
-4

Синтаксис выглядит как C ++, но идиома похожа на обычный старый Algol60. Было принято иметь такие блоки кода:

read n;
begin
    integer array x[1:n];
    ... 
end;

Пример можно записать так:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Я иногда скучаю по этому на современных языках;)

KDO
источник