Я стоял у истоков MISRA в роли одного из ревюьеров, но, как и многие другие Отцы языка С, я покинул это поделие уже после первой итерацуии. MISRA и подобное изначально является странной попыткой создания свода правил, в рамках которого низкоквалифицировнный персонал будет якобы создавать жизнеспособный код. То есть этот проект полностью посвящен той утопической идее, что ПТУшник с MISRA в бошке якобы будет условно равен квалифицированному программисту. Эта странная идея, разумеется, эпически провалилась под фанфары, как и все аналогичные проекты.
Однако в индустриях, создающих "автобус, поезд и такси" квалифицированные программистские кадры практически отсутствуют - туда просто никто не хочет идти работать. По каковой причине они выживают по модифицированному принципу: ПТУшник с MISRA - лучше чем ничего. Так что в некотором смысле вы правы: как бы катастрофически ни обстояли дела в индустриях, завязанных на MISRA, без MISRA их бы вообще не было.
Но мы здесь ведем речь о несравнимо более высоком уровне квалификации, чем целевая аудитория MISRA. Мы здесь все таки ведем речь о профессиональном высококвалифицированном использовании языка С. И в этой области мои рекомендации (в частности, комментарий наверху) обладают несравнимо большим авторитетом, чем некая "MISRA".
Написана какой-то набор слов. Размер функции определяется ее полезной функциональностью. Если функция должна быть большой - то она будет большой. Ее невозможно уменьшить.
Вопрос лишь в том, как эта большая функция будет записана в тексте программы: единым текстом или разобрана и разбросана по более мелким функциям. Так вот речь идет о том, что если это разбор и разбрасывание делались чисто из соображений "размера", то такие функции вообще невозможно будет ни читать, ни тестировать, ни поддерживать. В случае необходимости поддержки никто даже и пытаться не будет: просто выкинут из кода и перепишут заново.
Конструктор по умолчанию [...] инициализирует все члены класса согласно их типам (например, встроенные типы останутся не инициализированными, если это не bool, а объекты — будут инициализированы своими конструкторами по умолчанию).
Во-первых, что это за странная оговорка про bool ? Чем bool вдруг отличается от других?
Во-вторых, что это за странное использование термина "объекты"? А что объекты встроенных типов - это не объекты?
Конструктор копирования и оператор присваивания копированием по умолчанию будет выполнять побитовую копию всех полей.
Что за дичайшая дичь? Какая еще "побитовая копия"? Никогда и нигде в С++ не делаются никакие "побитовые копии", кроме разве что std::memcpy, которую вы сами руками вызвали .
Конструктор перемещения и оператор присваивания перемещением будут выполнять перемещение полей (перехват ресурсов).
"Перемещение" в С++ - концепция чисто пользовательского уровня. "Перемещение" может написать только вы своими руками. Конструктор перемещения и оператор присваивания перемещением будет делать именно и только то, что вы сами в нем напишете (за исключением автоматического делегирования в рамках "правила нуля"). "Встроенная" поддержка перемещения есть только у фундаментальных типов, и у них перемещение совпадает с копированием. Поэтому не ясно, о каком "будут выполнять перемещение" здесь идет речь.
По возможности стоит всегда инициализировать поля через списки инициализации
Ну вообще-то с С++11 у нас есть альтернативная возможность: указывать инициализаторы прямо в объявлениях полей, которая во многих случаях (в большинстве?) является лучшей идеей, чем ясное прописывание списков инициализации.
Первым делом обратим внимание на модификатор explicit. Хорошим тоном считается писать его в случаях, когда конструктор имеет лишь один параметр, потому что без него возможны неявные преобразования.
Приведенный пример кода как раз таки является примером того случая, когда позволить неявное преобразование было бы вполне уместным. Это explicit в данном случае никак не уменьшает вероятность неправильного использования (как в приведенном далее примере).
Перемещение через std::exchange
Непонятно тогда, почему в разделе про std::exchange при реализации оператора присваивания std::exchange не использовалось, а использовалась std::swap. Это совсем другая идиома. Так о какой именно идиоме вы хотели написать в этом разделе?
Перегрузка скобок
char& operator[](size_t i) { return *(str_ + i);}
Непонятно, почему в реализации используется странный инопланетный синтаксис *(str_ + i). Чем обычное человеческое str_[i] не угодило?
Вектор булевых значений
char* arr_;
Для представления бинарных данных использован тип char??? Не unsigned char?
Совершенно верно. Но логическая цепочка, которую я тут вижу, такова: идея разрешить end-итератору иметь тип, отличный от begin-итератора, родилась именно на основе концепции sentinels, то есть абстрактных "фиктивных" end-итераторов, которые служат лишь для того, чтобы проверить условие завершения в текущем итераторе. Считается, что sentinels возникли из Предложения N4382 Эрика Ниблера, из которого собственно и родились Ranges library (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4382.pdf). Финализирована разработка Ranges library была лишь к C++20, но поддержка самой идеи sentinels появилась в C++ уже в C++17.
Во-первых, ошибка форматированного ввода ставит поток в состояние fail, а не в состояние bad.
Во-вторых, не понимаю вопроса. Если вы хотите отличить - отличайте. Кто вам не дает? У вас есть доступ ко всем флагам и состояниям: и eof, и fail, и bad... Проверяйте на здоровье все, что хотите. Речь идет о том, что в корректном коде это делается уже после завершения чтения, а не для контроля самого цикла чтения. Как только вы начинаете это делать для контроля цикла чтения - с 99% вероятностью вы пишете ошибочный код.
В-третьих, все что я сказал выше про EoF, разумеется, касается всех функций проверки EoF. Неважно, feof это или что там есть в std::istream.
Тема с "проверкой на EOF" заезжена уже воль и поперек. В программах на стандартной библиотеке С и С++ не должно быть проверок на конец файла вызовом "функций проверки на eof" вроде feof или соотв. методов С++ потоков. За исключением ряда нишевых случаев такая проверка в коде является антипаттерном, т.е. сразу же является красным флагом ошибки.
Функции проверки состояния EoF в С и С++ предназначены для пост-мортемного анализа причин завершения чтения файла. В большинстве кода, как учебного, так и практического, никому такой пост-мортенмный анализ не нужен, поэтому и проверки состояния EoF в коде не должно быть вообще.
Стандартным корректным и хорошо устоявшимся паттерном чтения данных из файла в стандартной библиотеке С и С++ является паттерн "читаем, пока чтение проходит успешно". Успешность чтения проверяется на основе кодов возврата функций чтения. Это практически ясельное FAQ.
Обратите внимание (что, кстати, не отражено как следует в статье): файлы читают не до EoF, а до тех пор, пока не произойдет что-то исключительное, приводящее к отказу операции чтения. И это может быть далеко не только EoF, а также и физические проблемы ввода-вывода, и ошибки формата. Вот это - правильный паттерн, а не фиксация исключительно на EoF.
Основное отличие BFS от DFS только в том, что BFS используется очередь а DFS используется stack, в остальном алгоритм обхода практически не отличается
Грубейше не верно! Кто-то постоянно распространяет эту чушь среди студентов...
Классический алгоритм DFS, в частности по тому же Дийкстре, должен быть рекурсивным, то есть в нем должны быть четко выделенные этапы прямого и обратного хота, или, другими словами, этапы "раскраски" вершин: первой визит в вершину графа на прямом ходе алгоритма (белая вершина становится серой) и последний визит в вершину на обратном ходе алгоритма (серая вершина становится черной). Множество вторичных алгоритмов построено на основе дийкстровского DFS и используют именно эту структуру алгоритма с "перекраской" вершин.
Простой заменой очереди на стек вы не получите DFS из BFS. Вы получите некий псевдо-DFS, который правильно воспроизводит последовательность первых визитов в вершины, но не воспроизводит обратный ход алгоритма. Это не DFS, по крайней мере в каноническом понимании.
Это не более чем развитие идеи auto/decltype, то есть поддержки парадигмы type-agnostic кода. Появилась она в С++11. Парадигма эта существует со времен Царя Гороха, но массивным нововведениям в ее поддержку мы обязаны именно С++11.
Вложенные функции в С++ есть. Лямбда - это именно классическая вложенная функция. Вложенной функции, разумеется, нужен способ доступа к контексту. Для лямбды в С++ способ доступа к контексту реализован немного по-иному, чем, скажем, в Паскале, но способ реализации такого доступа - это деталь реализации, не играющая никакой роли.
C версии C++20 лямбду можно использовать вместе с шаблонами:
auto genericLambda = []<typename T>(T x) {
Ым... простите, но всякий раз, когда вы используете auto при объявлении типов в списке параметров лямбды, вы уже тем самым объявляете "шаблонную" (generic) лямбду, что стало возможно намного раньше: начиная С++14. Синтаксис с ключевым словом template, введенный в С++20 - это не более чем явный/"классический" вариант точно того же.
Более того, начиная с С++20, этот синтаксис синхронизирован и с обычными функциями, в списках параметров которых тоже можно использовать auto. Использование auto в списке параметров функции (не важно, обычной или лямбды) - это ни что иное, как сокращенный (abbreviated) синтаксис объявления шаблона.
Так что "шаблонность" в лямбдах присутствует с С++14, а не с С++20.
Итак, в C++11 тип возвращаемого значения лямбда-выражения нужно было указывать явно, если оно не было очевидным
Не понял. О чем тогда может идти речь в "если оно не было очевидным", как не об автоматическом выводе возвращаемого типа? Поясните, что вы имели в виду под "очевидностью" в С++11?
Настроим процесс стирания - укажем [...] сколько страниц это затронет. Последнее рассчитаем, исходя из размера структуры.
FlashErase.NbPages = structureSize / 1024 + 1;
Но, предположим, размер структуры равен ровно 1024. Какой результат нам даст такое выражение? Оно нам даст результат 2. А зачем нам стирать две страницы, если структура целиком помещается в одну?
По-моему, идиома целочисленного деления с округлением вверх широко известна:
Ну так в чем тут новость? Общеизвестно, что второе имя Линукса - это словосочетание "DLL Hell". Проблема эта существовала всегда, в этом нет ничего нового. Проблема только усугубилась после того, как статическая линковка с glibc стала deprecated.
Когда-то давно еще трепыхалась надежда, что проблему "DLL Hell" в Линуксе как-то удастся решить. Но в какой-то момент стало ясно, что в рамках линуксового подхода проблема неразрешима. С этого момента все усилия линуксовой коммьюнити были швырнуты на рекрутинг орд малолетних трололо на реддите, задачей которых было заорывание темы на публичных ресурсах, а частности, распространение бредней, которые вы все, я уверен, слышали: что, мол, "DLL Hell" - это, якобы, проблема Windows.
Этот момент и стал последним гвоздем в гроб Линукса.
Еще раз: задача состоит в том, чтобы реализовать составной функциональный макрос foo(x) так, чтобы его использование в вызывающем коде не отличалось от использования обычной функции.
"Составной" в данном случае значит, что макрос не может состоять из единственного выражения: так сложилось, что он должен включать несколько statements. Например, нам внутри зачем-то нужно объявить переменную.
"Не отличалось от использования обычной функции" означает, что, в частности, мы должны иметь возможность писать, например, такой код
if (условие)
foo(123);
else
foo(456);
Предложите ваши альтернативные варианты. Как вы будете реализовывать такой макрос без do while?
(Сразу скажу, что альтернативные варианты существуют, но do while, пожалуй, самый естественный из них.)
"Понравилась конструкция do {} while(0); в макросе" - это, наверное, одна из самых старых профессиональных шуток в стиле "рука-лицо" в мире программирования на С.
Весь смысл, вся цель, всё назначение конструкции do {} while(0) в макросах сводится к тому, чтобы не включать в нее эту замыкающую ;. Именно ради этого такая конструкция и используется в макросах. Вся идея держится на уникальном свойстве синтаксиса do/while - это "операторная скобка", которая требует замыкающей точки с запятой в конце. И вся суть тут в том, что ставиться эта точка с запятой будет пользователем снаружи макроса, а не внутри. (Можно предложить и другие варианты таких "операторных скобок", но они хуже, чем do/while).
Поэтому когда вам "нравится конструкция do {} while(0); в макросе" (именно в таком виде), вы либо ничего не поняли, либо как-то очень тонко троллите...
Copy-and-swap - известный паттерн реализации присваивания, но "стандартным" его называть странно. У этого паттерна много недостатков, из-за чего его можно применять только сознательно рассмотрев все "за" и "против". То есть использовать его в качестве паттерна "по умолчанию" ни в коем случае нельзя. Это, по-моему, уже исключает использование слова "стандартный".
Нет, мне 50. Почему внезапно 70?
Я стоял у истоков MISRA в роли одного из ревюьеров, но, как и многие другие Отцы языка С, я покинул это поделие уже после первой итерацуии. MISRA и подобное изначально является странной попыткой создания свода правил, в рамках которого низкоквалифицировнный персонал будет якобы создавать жизнеспособный код. То есть этот проект полностью посвящен той утопической идее, что ПТУшник с MISRA в бошке якобы будет условно равен квалифицированному программисту. Эта странная идея, разумеется, эпически провалилась под фанфары, как и все аналогичные проекты.
Однако в индустриях, создающих "автобус, поезд и такси" квалифицированные программистские кадры практически отсутствуют - туда просто никто не хочет идти работать. По каковой причине они выживают по модифицированному принципу: ПТУшник с MISRA - лучше чем ничего. Так что в некотором смысле вы правы: как бы катастрофически ни обстояли дела в индустриях, завязанных на MISRA, без MISRA их бы вообще не было.
Но мы здесь ведем речь о несравнимо более высоком уровне квалификации, чем целевая аудитория MISRA. Мы здесь все таки ведем речь о профессиональном высококвалифицированном использовании языка С. И в этой области мои рекомендации (в частности, комментарий наверху) обладают несравнимо большим авторитетом, чем некая "MISRA".
Написана какой-то набор слов. Размер функции определяется ее полезной функциональностью. Если функция должна быть большой - то она будет большой. Ее невозможно уменьшить.
Вопрос лишь в том, как эта большая функция будет записана в тексте программы: единым текстом или разобрана и разбросана по более мелким функциям. Так вот речь идет о том, что если это разбор и разбрасывание делались чисто из соображений "размера", то такие функции вообще невозможно будет ни читать, ни тестировать, ни поддерживать. В случае необходимости поддержки никто даже и пытаться не будет: просто выкинут из кода и перепишут заново.
И что? Почему "даже"? С каких это пор "MISRA" стала сколь-нибудь уважаемым источником в этом вопросе?
Во-первых, что это за странная оговорка про
bool
? Чемbool
вдруг отличается от других?Во-вторых, что это за странное использование термина "объекты"? А что объекты встроенных типов - это не объекты?
Что за дичайшая дичь? Какая еще "побитовая копия"? Никогда и нигде в С++ не делаются никакие "побитовые копии", кроме разве что
std::memcpy
, которую вы сами руками вызвали ."Перемещение" в С++ - концепция чисто пользовательского уровня. "Перемещение" может написать только вы своими руками. Конструктор перемещения и оператор присваивания перемещением будет делать именно и только то, что вы сами в нем напишете (за исключением автоматического делегирования в рамках "правила нуля"). "Встроенная" поддержка перемещения есть только у фундаментальных типов, и у них перемещение совпадает с копированием. Поэтому не ясно, о каком "будут выполнять перемещение" здесь идет речь.
Ну вообще-то с С++11 у нас есть альтернативная возможность: указывать инициализаторы прямо в объявлениях полей, которая во многих случаях (в большинстве?) является лучшей идеей, чем ясное прописывание списков инициализации.
Приведенный пример кода как раз таки является примером того случая, когда позволить неявное преобразование было бы вполне уместным. Это
explicit
в данном случае никак не уменьшает вероятность неправильного использования (как в приведенном далее примере).Непонятно тогда, почему в разделе про
std::exchange
при реализации оператора присваиванияstd::exchange
не использовалось, а использоваласьstd::swap
. Это совсем другая идиома. Так о какой именно идиоме вы хотели написать в этом разделе?Непонятно, почему в реализации используется странный инопланетный синтаксис
*(str_ + i)
. Чем обычное человеческоеstr_[i]
не угодило?Для представления бинарных данных использован тип
char
??? Неunsigned char
?Совершенно верно. Но логическая цепочка, которую я тут вижу, такова: идея разрешить end-итератору иметь тип, отличный от begin-итератора, родилась именно на основе концепции sentinels, то есть абстрактных "фиктивных" end-итераторов, которые служат лишь для того, чтобы проверить условие завершения в текущем итераторе. Считается, что sentinels возникли из Предложения N4382 Эрика Ниблера, из которого собственно и родились Ranges library (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4382.pdf). Финализирована разработка Ranges library была лишь к C++20, но поддержка самой идеи sentinels появилась в C++ уже в C++17.
Если тип объекта не поменялся (включая cv-квалификации), то
std::launder
не нужен.Однако вопрос о том, к чему тут эта странная деструкуция-конструкция вместо обычного присваивания, действительно стоит.
Во-первых, ошибка форматированного ввода ставит поток в состояние
fail
, а не в состояниеbad
.Во-вторых, не понимаю вопроса. Если вы хотите отличить - отличайте. Кто вам не дает? У вас есть доступ ко всем флагам и состояниям: и
eof
, иfail
, иbad
... Проверяйте на здоровье все, что хотите. Речь идет о том, что в корректном коде это делается уже после завершения чтения, а не для контроля самого цикла чтения. Как только вы начинаете это делать для контроля цикла чтения - с 99% вероятностью вы пишете ошибочный код.В-третьих, все что я сказал выше про EoF, разумеется, касается всех функций проверки EoF. Неважно,
feof
это или что там есть вstd::istream
.Тема с "проверкой на EOF" заезжена уже воль и поперек. В программах на стандартной библиотеке С и С++ не должно быть проверок на конец файла вызовом "функций проверки на eof" вроде
feof
или соотв. методов С++ потоков. За исключением ряда нишевых случаев такая проверка в коде является антипаттерном, т.е. сразу же является красным флагом ошибки.Функции проверки состояния EoF в С и С++ предназначены для пост-мортемного анализа причин завершения чтения файла. В большинстве кода, как учебного, так и практического, никому такой пост-мортенмный анализ не нужен, поэтому и проверки состояния EoF в коде не должно быть вообще.
Стандартным корректным и хорошо устоявшимся паттерном чтения данных из файла в стандартной библиотеке С и С++ является паттерн "читаем, пока чтение проходит успешно". Успешность чтения проверяется на основе кодов возврата функций чтения. Это практически ясельное FAQ.
Обратите внимание (что, кстати, не отражено как следует в статье): файлы читают не до EoF, а до тех пор, пока не произойдет что-то исключительное, приводящее к отказу операции чтения. И это может быть далеко не только EoF, а также и физические проблемы ввода-вывода, и ошибки формата. Вот это - правильный паттерн, а не фиксация исключительно на EoF.
Грубейше не верно! Кто-то постоянно распространяет эту чушь среди студентов...
Классический алгоритм DFS, в частности по тому же Дийкстре, должен быть рекурсивным, то есть в нем должны быть четко выделенные этапы прямого и обратного хота, или, другими словами, этапы "раскраски" вершин: первой визит в вершину графа на прямом ходе алгоритма (белая вершина становится серой) и последний визит в вершину на обратном ходе алгоритма (серая вершина становится черной). Множество вторичных алгоритмов построено на основе дийкстровского DFS и используют именно эту структуру алгоритма с "перекраской" вершин.
Простой заменой очереди на стек вы не получите DFS из BFS. Вы получите некий псевдо-DFS, который правильно воспроизводит последовательность первых визитов в вершины, но не воспроизводит обратный ход алгоритма. Это не DFS, по крайней мере в каноническом понимании.
Это не более чем развитие идеи
auto
/decltype
, то есть поддержки парадигмы type-agnostic кода. Появилась она в С++11. Парадигма эта существует со времен Царя Гороха, но массивным нововведениям в ее поддержку мы обязаны именно С++11.Вложенные функции в С++ есть. Лямбда - это именно классическая вложенная функция.
Вложенной функции, разумеется, нужен способ доступа к контексту. Для лямбды в С++ способ доступа к контексту реализован немного по-иному, чем, скажем, в Паскале, но способ реализации такого доступа - это деталь реализации, не играющая никакой роли.
Ым... простите, но всякий раз, когда вы используете
auto
при объявлении типов в списке параметров лямбды, вы уже тем самым объявляете "шаблонную" (generic) лямбду, что стало возможно намного раньше: начиная С++14. Синтаксис с ключевым словомtemplate
, введенный в С++20 - это не более чем явный/"классический" вариант точно того же.Более того, начиная с С++20, этот синтаксис синхронизирован и с обычными функциями, в списках параметров которых тоже можно использовать
auto
. Использованиеauto
в списке параметров функции (не важно, обычной или лямбды) - это ни что иное, как сокращенный (abbreviated) синтаксис объявления шаблона.Так что "шаблонность" в лямбдах присутствует с С++14, а не с С++20.
Не понял. О чем тогда может идти речь в "если оно не было очевидным", как не об автоматическом выводе возвращаемого типа? Поясните, что вы имели в виду под "очевидностью" в С++11?
Чего??? Из всех назначений лямбда-выражений, "вложенные функции" - самое основное.
Но, предположим, размер структуры равен ровно 1024. Какой результат нам даст такое выражение? Оно нам даст результат 2. А зачем нам стирать две страницы, если структура целиком помещается в одну?
По-моему, идиома целочисленного деления с округлением вверх широко известна:
FlashErase.NbPages = (structureSize + 1023) / 1024;
Ну так в чем тут новость? Общеизвестно, что второе имя Линукса - это словосочетание "DLL Hell". Проблема эта существовала всегда, в этом нет ничего нового. Проблема только усугубилась после того, как статическая линковка с glibc стала deprecated.
Когда-то давно еще трепыхалась надежда, что проблему "DLL Hell" в Линуксе как-то удастся решить. Но в какой-то момент стало ясно, что в рамках линуксового подхода проблема неразрешима. С этого момента все усилия линуксовой коммьюнити были швырнуты на рекрутинг орд малолетних трололо на реддите, задачей которых было заорывание темы на публичных ресурсах, а частности, распространение бредней, которые вы все, я уверен, слышали: что, мол, "DLL Hell" - это, якобы, проблема Windows.
Этот момент и стал последним гвоздем в гроб Линукса.
Мне не понятно, что именно вам непонятно.
Еще раз: задача состоит в том, чтобы реализовать составной функциональный макрос
foo(x)
так, чтобы его использование в вызывающем коде не отличалось от использования обычной функции."Составной" в данном случае значит, что макрос не может состоять из единственного выражения: так сложилось, что он должен включать несколько statements. Например, нам внутри зачем-то нужно объявить переменную.
"Не отличалось от использования обычной функции" означает, что, в частности, мы должны иметь возможность писать, например, такой код
Предложите ваши альтернативные варианты. Как вы будете реализовывать такой макрос без
do while
?(Сразу скажу, что альтернативные варианты существуют, но
do while
, пожалуй, самый естественный из них.)"Понравилась конструкция do {} while(0); в макросе" - это, наверное, одна из самых старых профессиональных шуток в стиле "рука-лицо" в мире программирования на С.
Весь смысл, вся цель, всё назначение конструкции
do {} while(0)
в макросах сводится к тому, чтобы не включать в нее эту замыкающую;
. Именно ради этого такая конструкция и используется в макросах. Вся идея держится на уникальном свойстве синтаксисаdo/while
- это "операторная скобка", которая требует замыкающей точки с запятой в конце. И вся суть тут в том, что ставиться эта точка с запятой будет пользователем снаружи макроса, а не внутри. (Можно предложить и другие варианты таких "операторных скобок", но они хуже, чемdo/while
).Поэтому когда вам "нравится конструкция do {} while(0); в макросе" (именно в таком виде), вы либо ничего не поняли, либо как-то очень тонко троллите...
Copy-and-swap - известный паттерн реализации присваивания, но "стандартным" его называть странно. У этого паттерна много недостатков, из-за чего его можно применять только сознательно рассмотрев все "за" и "против". То есть использовать его в качестве паттерна "по умолчанию" ни в коем случае нельзя. Это, по-моему, уже исключает использование слова "стандартный".