Чи є "нові" та "видалити" застарілі в 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 ++ дозволяє це, але це змусило мене думати, в яких випадках тоді потрібно використовувати динамічний розподіл? Або це компілятор перекладає це як динамічне розподілення?

Функція видалення включена. Зауважте, що тут йдеться не про витоки пам'яті.


54
У другому прикладі використовується масив змінної довжини, який ніколи не був частиною C ++. Для цього використовуйте std::vectorзамість ( std::vector<int> array(N);).
Якийсь програміст чувак

7
Пряма відповідь на ваше запитання повинна бути: ні, воно не застаріло. Незважаючи на те, що сучасні версії C ++ надають безліч функцій, що спрощують управління власністю на пам'ять (смарт-покажчики), все-таки поширеною практикою є розподіл об'єктів шляхом new OBJпрямого виклику .
pptaszni

8
Для інших людей, які плутаються з приводу того, чому люди говорять про витоки пам’яті, було відредаговано питання про виправлення помилки, яка не була важливою для запитання
Майк Карон

4
@Mannoj вважають за краще використовувати терміни Dynamic і Automatic для збирання та складання. Це рідко, але можливо реалізувати C ++ без купи і стеків.
користувач4581301

1
Ніщо ніколи не було застарілим у C ++ і нічого ніколи не буде. Це частина того, що означає C ++.
JoelFan

Відповіді:


114

Жоден фрагмент, який ви показуєте, не є ідіоматичним, сучасним кодом C ++.

newі deletenew[]і delete[]) не є застарілими в C ++ і ніколи не будуть. Вони все ще є способом інстанціювати динамічно виділені об'єкти. Однак, оскільки ви завжди повинні відповідати newз deletenew[]з а delete[]), їх найкраще зберігати в (бібліотечних) класах, які забезпечують це для вас. Див. Чому програмісти на C ++ мінімізують використання "нового"? .

Ваш перший фрагмент використовує "голий", new[]а потім ніколи не delete[]створює створений масив. Це проблема. std::vectorробить все необхідне тут просто чудово. Він використовуватиме певну форму newпозаду кулісу (я не буду заглиблюватися в деталі реалізації), але для всіх, що вам потрібно подбати, це динамічний масив, але кращий і безпечніший.

Ваш другий фрагмент використовує "масиви змінної довжини" (VLAs), функцію C, яку деякі компілятори також дозволяють використовувати в C ++ як розширення. На відміну від newVLA, по суті, виділяються на стеці (дуже обмежений ресурс). Але що ще важливіше, вони не є стандартною функцією C ++, і їх слід уникати, оскільки вони не є портативними. Вони, безумовно, не замінюють динамічного (тобто купи) розподілу.


3
Я хотів би додати, що хоча офіційні VLA офіційно не входять у стандарт, вони підтримуються всіма основними компіляторами, і тому рішення про те, уникати їх чи ні, є скоріше питанням стилю / уподобань, ніж реальною проблемою для портативності.
Stack Tracer

4
Також пам’ятайте, що ви не можете повернути масив або зберегти його в іншому місці, тому VLA ніколи не переживе час виконання функції
Руслан,

16
@StackTracer Наскільки мені відомо, MSVC не підтримує VLAs. І MSVC, безумовно, є "головним компілятором".
Макс Лангхоф

2
"Ви завжди повинні відповідати новому з видаленням" - не, якщо ви працюєте з ним Qt, так як у його базових класах усі є збирачі сміття, тому ви просто використовуєте newта забудете про це більшу частину часу. Для елементів графічного інтерфейсу, коли батьківський віджет закритий, діти виходять із сфери застосування та сміття збираються автоматично.
vsz

6
@vsz Навіть у Qt кожен newвсе ще має відповідність delete; це просто, що deletes виконує батьківський віджет, а не в тому ж блоці коду, що і news.
jjramsey

22

Ну, а для початку new/ deleteне стають застарілими.

У вашому конкретному випадку вони не єдине рішення. Те, що ви вибираєте, залежить від того, що заховано під коментарем "Зробіть щось із масиву".

У вашому 2-му прикладі використовується нестандартне розширення 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 ++.


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 []> (розмір) шукала мене дещо раніше.
Audrius Meskauskas

Справедливо! Я насправді make_shared<int[]>мало використовую , коли ти майже завжди хочеш vector<int>, але добре знати.
Саймон Бучан

Надмірна педантичність, але IIRC unique_ptrконструктор не перебуває, так що для Tцього немає конструкторів, що не мають расту, тому немає ніякого ризику протікань unique_ptr(new int[size])і shared_ptrмає таке: "Якщо викид викинуто, видаляється p, викликається, коли T не є типом масиву, видалити [ ] в іншому випадку ". Отже, ви маєте такий же ефект - ризик є для unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Саймон Бучан

3

нові та видалення не застаріли.

Об'єкти, створені новим оператором, можуть передаватися посиланням. Об'єкти можна видалити за допомогою delete.

new і delete - це основні аспекти мови. Наполегливістю об'єкта можна керувати, використовуючи новий та видалити. Вони точно не будуть застарілими.

Оператор - масив int [N] - це спосіб визначення масиву. Масив може використовуватися в межах блоку коду, що додається. Його не можна передавати, як те, як об’єкт передається іншій функції.


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'
    }
}

Я інколи пропускаю це в сучасних мовах;)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.