Комментарии 58
- Плюсы — это когда получение длины массива — тема для статьи
- Плюсы — это когда есть 3 способа создать массив, из которых 2 неправильных
P.S. Естественно, все это шутки, но пройти мимо не смог
std::array myArray = {2, 7, 1, 8, 2};
я так в коде и использую.
Мудрецы? Проблемы?)
есть еще
auto myArray = {2, 7, 1, 8, 2};
который создаете initializer_list у которого тоже можно брать размер. То что init_list — дерьмо, другой разговор, речь про то что «вот просто взять простым синтаксисом объявить » ;)
Почему же нельзя?
std::array arr = {1,2,3}
arr = [1, 2, 3]
И это уже объект массива, у него всякие методы можно вызывать. Теперь посмотрим на
std::array arr = {1,2,3}
Вот как мы объясним новичку, что тут происходит? Начать надо с std::. А для этого надо рассказать об областях видимости, неймспейсах и стандартной библиотеке. Рассказать, что лежит в std. Ладно, дальше array. А почему инициализируется списком в фигурных скобках {} а не как везде (и даже в С++ для «голых» массивов!) — квадратными? Надо рассказывать о списках инициализации. Потом объяснять, почему не скомпилилось, ведь надо ещё правильный include написать.
И ведь на всё есть свои причины! Из-за которых новичек посмотрит на это дело, плюнет, и уйдёт.
Почему для size везле int, а не size_t или auto?
2020 год же уже.
Страуструп и компания на одной из QnA сессий шесть лет назад ответили на этот вопрос. Где-то с 12:55 (сам вопрос на 9:50) и 43:14
https://youtu.be/Puio5dly9N8
Саттер: — Это неправильно.
Каррут: — Извините.
Чуть более развёрнутый ответ от Страуструпа:
— Это приводит к чрезмерно большому количеству ошибок.
Это приводит к необходимости преобразования типов, причём сплошь и рядом.
При этом при использовании беззнаковых типов индекс (unsigned)(-1) куда опаснее чем знаковый -1 потому что проверить его валидность сложнее.
Однако непонятно, почему. Unsigned позволяет как минимум не проверять переданный в публичном АПИ индекс на < 0.
Проверять на <0 бессмысленно, проверка всегда возвращает false.
А ошибки могут быть следующие:
1)
for(unsigned int i = 100500; i>=0; i--){...}
2)
unsigned int a = 3;
unsigned int b = 4;
unsigned int i = a - b;
if(i < 0) return;
... use arr[i] ...
Это даже если signed и unsigned не смешивать.
Если публичное API signed
Речь шла о unsigned. Будет тот же sigsegv (ибо в дополнительном коде a+((unsigned int)-1) == a-1
, но проверкой <0 такой "-1" не обнаружить, он равен например 0xffffffff.
Во всех случаях — ошибка программиста программы, а не программиста API.
Ошибки — они и есть ошибки. Их надо исправлять, а не перекладывать.
P.S. Кто такой "программист программы" и чем он отличается от обычного программиста?
Касательно проблем с «нельзя отвалидировать индекс» — а разве то, что другой программист прислал в ваше публичное АПИ некорректный индекс — это проблема создателя публичного АПИ? Это проблема ТОЛЬКО того программиста, который написал некорректный код
В некотором роде, это проблема создателей языка, когда i>=0 то работает, то не работает в зависимости от типа i. Та же java намеренно не имеет числовых беззнаковых типов, чтобы быть надёжнее проще для не всегда внимательных кодеров.
Проверив индекс на < 0 если он сделал какую-то математику вы только в лучшем случае заглушите ошибку.
Ну в реальном коде я бы там исключение бросал, чтобы наверняка, это лишь пример с return. SIGSEGV-то не при каждом доступе за пределами массива возникает, так что это ненадёжная проверка, что ошибок нет. Тут вопрос в том, что проверка в моём примере вообще нерабочая.
В некотором роде, это проблема создателей языка, когда i>=0 то работает, то не работает в зависимости от типабеззнаковость — это дополнительная информация, которую вы и тулинг можете использовать. Ваше «i>=0 не работает» — очень странная претензия. Вам банально компилятор или любой анализатор выдаст предупреждение об избыточности условия. Вы ведь не обязательно хотели итерироваться до -1, возможно, вы хотели написать «i > 0» или «i != 0», или что-нибудь еще в таком духе.
Вы ведь не обязательно хотели итерироваться до -1, возможно, вы хотели написать «i > 0» или «i != 0»,
Нет, я хотел итерироваться до 0 включительно (не до 1 и не до -1), поэтому написал i>=0. При чем тут i>0 и i!=0? В этом случае описанной проблемы не было бы. Проблема в i>=0.
Да, к слову, проблемы возникают и на граничных значениях signed типов (abs(INT_MIN) не влезает в int), и на пределах точности float-типов. Но у unsigned граница 0, и это очень близко по величине к тем значениям, которые обычно используют, поэтому проблемы тоже находятся ближе.
Нет, я хотел итерироваться до 0 включительно (не до 1 и не до -1)в случае signed счетчика ваш цикл остановится на значении -1.
При чем тут i>0 и i!=0? В этом случае описанной проблемы не было бы. Проблема в i>=0.проблема в вас — вы хотите чтобы программа вела себя не так как вы её написали.
Да, к слову, проблемы возникают и на граничных значениях signed типов (abs(INT_MIN) не влезает в int)это особенность двоичного представления целых чисел в общем, и вообще никак не относится к беззнаковым
Но у unsigned граница 0, и это очень близко по величине к тем значениям, которые обычно используют, поэтому проблемы тоже находятся ближе.какая разница, пытаетесь вы обратиться к -1-му или 9'223'372'036'854'775'807-му элементу массива, если и то и другое — UB?
Что до подсчета, то компьютеры в этом чертовски хороши. Так пусть же считает компьютер!
А если нужно задать массив из 100 элементов, где первые {1,2,3}, а остальные нули?
А если вообще нужен неинициализованный массив?
По-моему вредность этого «антипаттерна» в представлении автора (заметил, что перевод) сильно преувеличена.
Это должно быть переписано по крайней мере как:
std::array<int, 5> myArray = {2, 7, 1, 8, 2};
Ну то есть когда пишем просто int myArray[5] — «магическое число» это плохо, а вот когда std::array<int, 5> — это уже совсем другое дело…
А если нужно задать массив из 100 элементов, где первые {1,2,3}, а остальные нули?
А если вообще нужен неинициализованный массив?
По-моему вредность этого «антипаттерна» в представлении автора (заметил, что перевод) сильно преувеличена.
Пойнт как раз в том, чтобы отличать случаи "мне нужен массив конкретной длины, вот инициализация его части" от случая "мне нужен массив с вот таким содержимым".
А если нужно задать массив из 100 элементов, где первые {1,2,3}, а остальные нули?
Иногда чтение кода на c++ напоминает мне чтение какого нибудь Толстого, Достоевского, Чехова… Всегда при втором прочтении обнаруживаются скрытые смыслы и акценты. А что имел ввиду автор? Умышленно ли он заставил говорить своего героя эту фразу? Или это ничего не значащий эпизод из трехтомника?
Ну то есть когда пишем просто int myArray[5] — «магическое число» это плохо, а вот когда std::array<int, 5> — это уже совсем другое дело…во втором случае у вас всегда будет arr.size(), который вернет то самое волшебное число. Даже при попытке передать этот массив куда-то.
int myArray[] = {2, 7, 1, 8, 2}; constexpr int myArraySize = std::size(myArray);
Это требует C++17. Вариант ниже сработает на любой кофеварке и в обычном C:
int myArray[] = {2, 7, 1, 8, 2};
int myArraySize = sizeof(myArray)/sizeof(myArray[0]);
А потом перенесли myArray
в кучу и привет.
И-и-и… Что?
int *myArray = (int *)malloc(5 * sizeof(int));
int notMyArraySize = sizeof(myArray)/sizeof(myArray[0]);
Тогда непонятно, зачем размер массива второй раз вычислять, если он заранее, на этапе malloc(), известен.
Конечно когда я показал вам две строчки то всё понятно. Но такие ошибки встречаются, когда что-то поменяли, но забыли сделать во всех местах. Особенно у новичков, но и опытные не застрахованы от глупых ошибок. Обычно компилятор должен помогать в таких случаях, потому что по факту тип переменной поменялся. В С++ он действительно меняется с std::array на std::vector, например. А в Си всё есть указатель причем в худшем случае ещё и на void*
, т.е. следить надо самому.
Ещё есть std::experimental::make_array
:)
А я вот с примером с сообщением не согласен, что это вредно.
В программировании микроконтроллеров часто так делаю: выделяю буфер с запасом небольшим, в котором лежит сообщение. Потом, при необходимости, буфер переписываю другим сообщением.
выделяю буфер с запасом небольшимЭтот бред уже вошёл в анналы истории:
gamedev.ru/flame/forum/?id=122958
Делаю так, чтобы помещалось ;-)
Свежий пример: логирование цепочки датчиков температуры в порт. Буфер, условно, 100 байт. Один буфер — одна строчка лога. Сначала "шапка" (время и т.п.), потом, строчка за строчкой, показания датчиков. Заполнили, отправили. Если совсем никак не влезает, бьём на две отправки, и т.п.
Да в некоторых протоколах у приемника бывает возможность управления потоком — как-то попросить передатчик приостановить передачу. Но далеко не во всех.
Вообще, я привёл один из многих примеров, когда «антипаттерн», а котором так категорично рассуждает автор, совсем не является проблемой, а скорее, наоборот.
Вы же мне предлагаете рассмотреть вообще все возможные случаи, когда приведённый мною пример не работает?
Не существует какого-то универсального решения или подхода, который был бы абсолютно правильным. Мир разнообразен, и в каждом случае наиболее подходящее решение будет своё. Более того, одному разработчику в данном конкретном случае кажется правильным одно решение, другому — совсем другое. И это нормально.
А вот категорично рассуждать, подобно автору, о том, что вот данное решение всегда неправильное, я бы не стал.
Другой МК решение, да. Но это значит и вносить (и согласовывать) изменения в конструкцию, план закупок и т.п. Но это еще фигня.
Куда девать уже имеющийся закупленный запас контроллеров? Хотя и это вопрос не принципиальный, а чисто коммерческий (хотя начальство уже будет недовольно).
Принципиально же то, что на уже выпущенных девайсах прошивку не проапгрейдишь.
Правда, хоть проблем с обратной совместимостью не было, так что переползти на другие контроллеры оказалось легко (с архитектуры mcs51 на AVR).
На противоречие «новые фичи vs обратная совместимость» напоролись уже в другом случае, хотя и без замены контроллера. И начальство выбрало новые фичи.
Но напоминаю, эта ветка комментов началась с «просто выделяю буфер с запасом» и вопроса «а как быть если новое сообщение не помещается в буфер». Тут я, конечно, несколько додумал возможный ответ «еще увеличить буфер» и намекнул, что увеличение буфера довольно быстро упирается в конечный размер памяти, каковой обычно в МК очень небольшой.
Конечно, на этапе проектирования это еще вообще не проблема — вполне можно проанализировать, а какой самый большой размер сообщения надо обрабатывать — и соответственно выбрать контроллер (или добавить внешнюю память). Проблемы начинаются именно когда надо внести доработки по хотелкам заказчика. И даже выбор «с запасом» плохо помогает. Ну сколькикратный запас можно заложить? пятикратный? десятикратный? А ведь может внезапно всплыть, что если раньше сообщения ограничивались сотнями байт, то в новом протоколе могут оказаться и десятки килобайт (под длину выделено уже два байта). Да, в итоге вполне можно прийти к какому-то решению — от оптимизации архитектуры обработки до замены элементной базы. Но в первый момент, когда такое требование приходит — возникает естественная реакция схватиться за голову и произнести короткое «антидепрессивное заклинание».
проблема ли это конечного программистаЗависит от стиля руководства. Если «начальник сказал — хорёк! и никаких сусликов», то или как-то решаешь эту проблему или остаешься без премии. Ну или плюёшь на такие закидоны и идешь искать другую работу с адекватным начальством (что может оказаться куда большей проблемой).
Заказчику-то, надо сказать, обычно плевать, какой контроллер внутри и вообще содержимое «коробочки». Его волнует функциональность и таки взаимозаменяемость — чтобы можно было новую коробочку взять и поставить вместо старой, и при этом не перелопачивать свою систему.
И да, «нахрен всё, делаем новый продукт с отличающимся обозначением, уговорив заказчика» у нас тоже было. Поскольку позволить установить вместо новой старую коробочку со склада тоже нельзя — иначе возникают претензии «ваша железка не работает как надо!».
И посадочные места под внешнюю память «на будущее» мы как-то тоже делали… Наряду с местами под цепи дополнительных входов и выходов. На случай «вдруг еще что заказчику в голову взбредет, а у нас на складе запас плат не израсходован». Когда заказчик — большой завод, а ты — довольно мелкая компания, приходится плясать перед ним на цыпочках. Поскольку другую такую контору по разработке он найдет, а вот мы другой такой завод — нет. Так что вполне можно понять руководство, которое заглядывает заказчику в рот и по любому его почесыванию «а неплохо бы вот еще так сделать» берет под козырек и спускает ТЗ отделу разработки. А заказчик потом «а не, так не надо, давайте по-другому»… ну да ладно, хватит о грустном.
Надо сказать, лишние посадочные места себестоимости не добавляют, в отличие от контроллера, выбранного с большим запасом.
В общем, возвращаясь к теме — суть в том, что разработка на МК все же куда ограниченнее в применении экстенсивных методов. На ПК запас под «увеличение буфера» и т.п. находится в подавляющем большинстве случаев, изменение объемов постоянно хранимой информации тоже довольно легко масштабируется (хотя пределы тоже есть, но подавляющее большинство задач в них влезает со свистом).
суть в том, что разработка на МК все же куда ограниченнее в применении экстенсивных методовС этим и не собирался спорить :)
А я вот с примером с сообщением не согласен, что это вредно.
Это вредно именно потому, что не понятно, то ли буфер выделен с запасом специально, а то ли по ошибке.
Приведенный выше код столь же ужасен с точки зрения обслуживания, как:Совершенно не согласен.
Ключевая разница — в том, что второй кусок данных константный, в то время как данные в массиве myArray могут быть модифицированы (и, по-нормальному, должны быть — иначе и их лучше сделать константными).
Алгоритмы вида x[n+2]=a*x[n+1]+b*x[n] встречаются сплошь и рядом.
Поэтому я настоятельно предпочитаю T[] более новому std::array<T, N>.std::array всё еще надежнее как минимум потому, что не приводится неявно к указателю и нормально копируется/мувится. И бонусом сверху есть некоторый синтаксический сахар, типа операторов лексикографического сравнения, structured bindings и пр.
Антипаттерн “константа размера массива”