- PVSM.RU - https://www.pvsm.ru -
C/C++ позволяют выполнить проверки константных выражений ещё на этапе компиляции программы. Это дешёвый способ избежать проблем при модификации кода в будущем.
Я рассмотрю работу с:
Существуют много способов [1] сломать компилятор во время компиляции. Из них мне больше всего нравится такое исполнение:
#define ASSERT(cond) typedef int foo[(cond) ? 1 : -1]
Но если у вас в программе используется boost, то ничего изобретать не нужно: BOOST_STATIC_ASSERT [2]. Так же поддержка обещает быть [3] в С++11 (static_assert).
С инструментом разобрались, теперь об использовании.
Перечисления — набор связанных по смыслу констант, которые, как правило, используются в точке ветвления логики программы. Точек ветвления обычно несколько, и можно легко что-нибудь пропустить.
Пример:
enum TEncryptMode {
EM_None = 0,
EM_AES128,
EM_AES256,
EM_ItemsCount
};
Последний элемент — не алгоритм, а вспомогательная константа с номером на единицу большим, чем максимальный смысловой элемент.
Теперь везде, где используются константы из этого набора, нужно просто добавить проверку:
ASSERT(EM_ItemsCount == 3);
Если в будущем добавятся новые константы, код в этом месте перестанет компилироваться. Значит, автор изменений должен будет просмотреть этот участок кода и, при необходимости, учесть новую константу.
В качестве бонуса от введения EM_ItemsCount появляется возможность вставлять runtime-проверки параметров функции:
assert( 0 <= mode && mode < EM_ItemsCount );
Сравните с вариантом без такой константы:
assert( 0 <= mode && mode <= EM_AES256 );
(добавляем EM_AES512 и получает неправильную проверку)
Частный случай проверки из предыдущего раздела.
Предположим, у нас есть массив с параметрами к тем же алгоритмам шифрования (пример немного высосан из пальца, но в жизни встречаются похожие случаи):
static const ParamStruct params[] = {
{ EM_None, 0, ... },
{ EM_AES128, 128, ... },
{ EM_AES256, 256, ... },
{ -1, 0, ... }
};
Требуется поддерживать эту структуру синхронной с TEncryptMode.
(Зачем нужен последний элемент массива, думаю, объяснять не нужно.)
Нам понадобится вспомогательный макрос для вычисления длины массива:
#define lengthof(x) (sizeof(x) / sizeof((x)[0]))
Теперь, можно записать проверку (лучше, если сразу за определением params):
ASSERT( lengthof(params) == EM_ItemsCount + 1 );
Тут всё очевидно (после примеров выше). Перед switch(mode) добавляем:
ASSERT(EM_ItemsCount == 3);
Чуть менее очевидная runtime-проверка:
ASSERT(EM_ItemsCount == 3);
switch( mode ) {
case ...: ... break;
...
default:
assert( false );
}
Дополнительный бастион для обороны от ошибок. Если действия обрабатываются одинаково, лучше перечислить несколько case-условий для одного действия, оставив default не занятым:
...
case ET_AES128:
case ET_AES256:
...
break;
...
Отвлечёмся от enum'ов и посмотрим на такой класс:
class MyData {
...
private:
int a;
double b;
...
};
Очень может быть, что когда-то в будущем кто-то захочет добавить в него переменную int c. Класс к этому времени стал большим и сложным. Как найти точки, в которые нужно прописать переменную c?
Предлагается такой полуавтоматический способ решения — заводим в классе константу версии данных:
class MyData {
static const int DataVersion = 0;
...
};
Теперь во всех методах, в которых важно отследить целостность всех данных, можно прописать:
ASSERT(DataVersion == 0);
Добавляя новые данные в класс, придётся вручную увеличить константу DataVersion (тут требуется дисциплина, увы). Зато компилятор сразу обратит внимание на те места, которые нужно проверить. К таким точкам проверки должны относиться:
Остальные места проверки зависят от внутренней логики (вывод в лог, например).
Эту же константу (DataVersion) удобно использовать при сохранении данных на диск (если интересно, могу написать об этом отдельно).
Что в итоге?
Плюсы:
Минусы:
Для меня плюсы перевешивают, а для вас?
Автор: to_climb
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-plus-plus/2411
Ссылки в тексте:
[1] много способов: http://stackoverflow.com/questions/1980012/boost-static-assert-without-boost
[2] BOOST_STATIC_ASSERT: http://www.boost.org/doc/libs/1_48_0/doc/html/boost_staticassert.html
[3] обещает быть: http://ru.wikipedia.org/wiki/C%2B%2B11
Нажмите здесь для печати.