Тіпобезопасние прапори на базі enum

При роботі з мікроконтролерами на низькому рівні дуже часто доводиться працювати з окремими бітами в конфігураційних регістрах, з різними прапорами станів і т.д. Традиційно в мові «С» для цього використовуються цілочисельні змінні і бітові операції з ними. Все ніби-б просто і зрозуміло, що ще потрібно? Тіпобезопасность. Цілочисельний змінної, використовуваної для прапорів, можна привласнити будь-неосмислене число, і компілятор абсолютно не буде проти.

Перейдемо до практики. Символічні імена для прапорів зручно оголосити за допомогою enum-а:

Типова целочисленная флаговая змінна:

Поки все виглядає чисто і красиво, але нічого не заважає нам писати так:

Які там прапори були скинуті, встановлені? Компілятор все з'їсть і не подавиться, myFlags - целочисленная змінна і може зберігати що завгодно. Але ж, іноді, здається так просто замість довгого рядка, види: flag0 | flag1 |. | flagM написати одне коротеньке число, яке здається таким очевидним ... Зараз - очевидним, завтра для себе-ж воно вже нічого не означатиме, а для інших і поготів.
Хочеться обгородити себе від таких «зайвих можливостей», щоб робота з прапорами була більш суворою. Очевидно, що в такому випадку, зберігати прапори, потрібно не в цілочисельний змінної, а в змінної типу enum-а - MyFlags.

Перший рядок працює як очікується, змінної flags можна привласнити аби-що - тільки елементи перерахування. Але тут нас чекає одна неприємність, вираз flag0 | flag1 (як і інші бітові операції з перерахуваннями) має тип int і другий рядок викличе помилку компіляції. Доведеться використовувати приведення типів, але робити це явно при кожній операції з прапорами чи не краще, ніж зберігати прапори в int:

Можна заховати ці перетворення типу в спеціальні функції (тільки не в макроси, інакше весь контроль типів піде лісом):

Написати таких функцій для якогось розумного числа аргументів і користуватися:

Начебто вже не погано - своєї мети, тіпобезопасності, ми вже досягли. На С ++ можна зробити ще краще - просто перевантажити для нашого флагового еnum-а бітові операції:

Приведення аргументів до int в даному випадку потрібно, щоб не було нескінченної рекурсії :) Тепер користуватися прапорами стало набагато зручніше:

Для зручності подальшого використання запіхнём перевантажені оператори для enum-ів в макроозначень:

Тепер для користування тіпобезопасних прапорів достатньо написати відповідну перерахування і задати для нього бітові операції:

Такими прапорами, наприклад, зручно користуватися для ініціалізації різної периферії. Приклад ініціалізації модуля USART в STM32:

Як на рахунок ситуації, коли Ви створюєте основу, архітектуру проекту, а завершують його реалізацію і супроводжують його інші люди? Чим менше їм буде можливостей напортачить, тим більше шансів, що проект залишиться цілісним і самоузгодженим. У вас, мабуть, маленький досвід командної роботи :)

да тут у більшості він не особливо великий

Ололо, я таки не помилився щодо командної роботи?) Щодо закладання архітектури, яку реалізують інші, часто індуси, я теж подумав.

Явні приведення типів в даному випадку будуть відразу кидатися в очі і їх, на відміну від целоцісленной константи, легко знайти пошуком за допомогою регулярного виразу.
Якщо можна себе і інших трішки опезопасіть від таких речей, причому безкоштовно, чому цього не зробити?

Ну, простота в тому числі сприяє читабельності і підтримувані. Якщо звичайно під простотою не брати до уваги 0x2F замість flag1 | flag2 | flagN.
Але в моєму розумінні красиво - це просто, технічно витончено і читаемо.

Не менш ефективні програми дозволяє писати і старий-добрий Сі. А якщо не видно різниці ... Не менш ефективні програми дозволяють писати старі добрі виконавчі машинні коди. Ну ладно, щодо добрих я жену. Але і С ++ в умілих руках тим же від С відрізняється.

Ех, дивлячись на цю «простоту» плакати хочеться. (
Мля, навіть біт в регістрі не можете встановити, чи не навернуться кілометр оболонок навколо елементарних оператора | =.
Алсо городити безглузді обмеження - це не C-way. Алсо щоб не юзати 0x2F замість flag1 | flag2 | flagN потрібно мізків значнтельно менше ніж щоб вкурил в усі ці нетрі коду на сраним Сі ++.
Загалом, мені з вами не попуті. /

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

Пару раз шмякнуть лобом об клаву і писатимуть нормально. )
А зайвий раз без необхідності перевантажувати стандартні оператори не потрібно. Не потрібно робити речі більш складними і заплутаними просто тому, що можеш.

Якби все було так просто, Вірт б не розробляв все нові мови, що не дозволяють вистрілити собі в ногу :)

Якщо я правильно зрозумів англійський текст то в ньому, по union-ам, не сказано нічого нового.
І так зрозуміло, що під час запису float в union можна прочитати як int в надії отримати цілу частину числа. Навіть якщо union складається з float і int.

пс. якщо використовувати безіменну структуру, то, запис прапорів вийде простіше


Про Місрієв нічого не знаю, крім того що вона є.

Ні. Записавши float в union, не можна очікувати, що з int ти прочитаєш reinterpret_cast (float). І якщо ти пишеш flag.f = 0 - то немає ніякої гарантії, що це скине бітові прапори. Так, звичайно це працює, але згідно зі стандартом це UB.

Записавши float в union, не можна очікувати, що з int ти прочитаєш reinterpret_cast (float).
Незрозуміло для чого може знадобиться такий cast. Є ж окремий доступ до int і окремо до float.
Або ти про те що не можна визначити що саме зберігається всередині union в кожен момент часу - int або float? Це так.
lag.f = 0 - то немає ніякої гарантії, що це скине бітові прапори. Так, звичайно це працює, але згідно зі стандартом це UB. З бітовими полями UB не менш проблем доставляє.