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

Почему С++ не поддерживает диапазон, основанный на цикле для динамических массивов?

Почему С++ не поддерживает диапазон, основанный на цикле над динамическими массивами? То есть, что-то вроде этого:

int* array = new int[len];
for[] (int i : array) {};

Я только что придумал инструкцию for[], чтобы рифмовать с помощью new[] и delete[]. Насколько я понимаю, среда выполнения имеет размер доступного массива (иначе delete[] не может работать), поэтому теоретически диапазон, основанный на цикле, также можно заставить работать. В чем причина, по которой он не работал?

4b9b3361

Ответ 1

int* array = new int[len];
for[] (int i : array) {}

Существует несколько моментов, которые необходимо решить; Я буду решать их по очереди.

Знает ли время выполнения размер массива?

В определенных условиях он должен. Как вы указали, вызов delete[] вызовет деструктор каждого элемента (в резервном порядке) и, следовательно, должен знать, сколько его есть.

Однако, не указывая, что количество элементов должно быть известно и доступно, стандарт С++ позволяет реализации опускать его всякий раз, когда вызов деструктора не требуется (std::is_trivially_destructible<T>::value оценивается как true).

Может ли время выполнения различать указатель и массив?

В общем, нет.

Когда у вас есть указатель, он может указывать на что-либо:

  • отдельный элемент или элемент в массиве
  • первый элемент в массиве или любой другой
  • массив в стеке или массив в куче,
  • просто массив или часть массива более крупного объекта.

Именно по этой причине delete[] существует, и использование delete здесь было бы неверным. С помощью delete[] вы указываете состояние пользователя: этот указатель указывает на первый элемент массива, выделенного кучей.

Затем реализация может предположить, что, например, в 8 байтах, предшествующих этому первому элементу, он может найти размер массива. Без вас, гарантируя это, эти 8 байтов могут быть чем угодно.

Тогда почему бы не пройти весь путь и не создать for[] (int i : array)?

Есть две причины:

  • Как уже упоминалось, сегодня реализация может превышать размер по ряду элементов; с этим новым синтаксисом for[], это было бы невозможно для каждого типа.
  • Это не стоит.

Давайте будем честными, new[] и delete[] являются реликтами более старого времени. Они невероятно неудобны:

  • количество элементов должно быть известно заранее и не может быть изменено,
  • элементы должны быть по умолчанию конструктивными или иначе C-ish,

и небезопасно использовать:

  • количество элементов недоступно для пользователя.

Как правило, нет смысла использовать new[] и delete[] в современном С++. В большинстве случаев предпочтительнее a std::vector; в немногих случаях, когда емкость избыточна, a std::dynarray все еще лучше (потому что он отслеживает размер).

Поэтому, без уважительной причины продолжать использовать эти утверждения, нет мотивации включать новые семантические конструкции, специально предназначенные для их обработки.

И должен ли кто-нибудь быть достаточно мотивированным, чтобы сделать такое предложение:

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

Я рекомендую вам просто использовать std::vector.

Ответ 2

В чем причина, по которой он не работал?

Цикл, основанный на диапазоне, например

 for(auto a : y) {
     // ...
 }

является просто синтаксическим сахаром для следующего выражения

 auto endit = std::end(y);
 for(auto it = std::begin(y); it != endit; ++it) {
     auto a = *it;
     // ...
 }

Так как std::begin() и std::end() не могут использоваться с простым указателем, это не может быть применено с указателем, выделенным с помощью new[].

Насколько я понимаю, среда выполнения имеет размер доступного массива (иначе delete[] не может работать)

Как delete[] отслеживает блок памяти, который был выделен new[] (который не обязательно имеет тот же размер, который был указан пользователем), это совершенно другая вещь, и компилятор, скорее всего, не даже знаю, как именно это реализовано.

Ответ 3

Если у вас есть это:

int* array = new int[len];

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

Для диапазона, основанного на работе, для компилятора нужны два адреса, начало и конец массива.

Итак, проблема заключается в том, что компилятор не имеет достаточной информации для этого:

// array is only a pointer and does not have enough information
for(int i : array)
{
} 

Ответ 4

Это не связано с динамическими массивами, оно более общее. Конечно, для динамических массивов существует где-то размер, чтобы иметь возможность вызвать деструкторы (но помните, что стандарт ничего не говорит об этом, просто вызов delete [] работает по назначению).

Проблема заключается в указателях в целом, так как задан указатель, который вы не можете сказать, соответствует ли он любому типу... что?

Массивы распадаются на указатели, но дают указатель, что вы можете сказать?

Ответ 5

array не является массивом, а указателем и нет информации о размере для "массива". Таким образом, компилятор не может выводить begin и end этого массива.

См. синтаксис диапазон для цикла:

{
   auto && __range = range_expression ; 
   for (auto __begin = begin_expr, __end = end_expr; 
   __begin != __end; ++__begin) { 
   range_declaration = *__begin; 
   loop_statement 
   } 
} 

range_expression - любое выражение, которое представляет подходящую последовательность (либо массив, либо объект, для которого функции начала и конца члена или бесплатные функции, см. ниже) или список с расширенными командами.

auto работает во время компиляции. Таким образом, begin_expr и end_expr не вычитают время выполнения.

Ответ 6

Причина в том, что, учитывая только значение указателя array, компилятор (и ваш код) не имеет информации о том, на что он указывает. Известно только, что array имеет значение, которое является адресом одного int.

Он может указывать на первый элемент статически выделенного массива. Он может указывать на элемент в середине динамически распределенного массива. Он может указывать на элемент структуры данных. Он может указывать на элемент массива, который находится внутри структуры данных. Список продолжается.

Ваш код сделает ПРЕДПОЛОЖЕНИЯ о том, на что указывает указатель. Он может считать, что это массив из 50 элементов. Ваш код может получить доступ к значению len и принять array точки в (первом элементе) массива элементов len. Если ваш код правильно, все работает по назначению. Если ваш код ошибочно (например, обращается к 50-му элементу массива с 5 элементами), то поведение просто undefined. Это undefined, потому что возможности бесконечны - ведение бухгалтерского учета для отслеживания того, на что указывает произвольный указатель, НАСТОЯЩИМ указывает (помимо информации, что есть int на этом адресе) будет огромным.

Вы начинаете с ПРЕДПОЛОЖЕНИЯ, что array указывает на результат от new int[len]. Но эта информация не сохраняется в значении array, поэтому компилятор не имеет возможности вернуться к значению len. Это будет необходимо для вашего подхода, основанного на диапазоне.

Пока да, данный array = new int[len], механизм, вызываемый delete [] array, будет работать, чтобы array имел элементы len и освободил их. Но delete [] array также имеет поведение undefined, если array получается из чего-то другого, кроме выражения new []. Даже

  int *array = new int;
  delete [] array;

дает поведение undefined. "Время выполнения" не требуется для разработки, в данном случае, что array на самом деле является адресом одного динамически распределенного int (а не фактического массива). Таким образом, это не требуется, чтобы справиться с этим.