Страницы

Поиск по вопросам

четверг, 12 декабря 2019 г.

Контейнер для констант

#cpp


Есть список UnicodeString констант и соответствующий ему список целочисленных констант:

const UnicodeString ERR = L"Error";
const UnicodeString READY = L"Ready";
...
const int S_ERR = 0;
const int S_READY = 1;
...


Не знаю, какой контейнер для хранения и удобной работы с этими данными использовать.
Если использовать их отдельно, то получается крайне неудобно с ними работать, когда
надо получать строку в зависимости от состояния и подобные действия:

switch (Status) {
case S_ERR: 
return ERR;
...
}


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


Ответы

Ответ 1



Раз у Вас есть ассоциативное отношение, то сам собой напрашивается ассоциативный контейнер, а именно: std::unordered_map: ... const int S_ERR = 0; const int S_READY = 1; ... std::unordered_map strings; strings.emplace(S_ERR, L"Error"); strings.emplace(S_READY, L"Ready"); ... return strings[Status]; Это позволяет нам не задумывать о том, в каком порядке, что хранится и не пользоваться switch, который только раздувает код.

Ответ 2



Если константы идут строго подряд - std::vector (std::array). Если с дырами - std::map Пример с вектором (ideone) #include #include using namespace std; static constexpr array strs ={L"azaza",L"ololo"}; int main() { for(const auto i:strs) { wcout << i << endl; } return 0; } А для локализации существует PoEdit и связанный с ним фреймворк.

Ответ 3



Применение контейнеров в данном простом случае избыточно. Для решения описанной вами задачи, я определяю перечисление с числовыми константами и пишу функцию которая выполняет сопоставление чисел и строк. Теперь необходимо следить за этим сопоставлением. Чтобы о нем не забыть, я добавляю assert в функции сопоставления плюс вывожу какую-либо заметую строку. Assert срабатывает в отладочной сборке, а "заметная строка" в выпускной сборке. При таком подходе, за мою практику ниразу в выпускной сборке не появлялись несопоставленные константы, 100% забытых сопоставлений отлавливались на этапе отладки с помощью assert. Дополнительно, обычно если значения приходят откуда-то извне из файла или из сети, я пишу функцию проверки значений на соответсвие константам. Вот примерно такой код получается: enum StatusCodes { STATUS_SUCCESS , STATUS_WARNING , STATUS_ERROR , STATUS_CRASH , STATUS_UNKNOWN }; const wchar_t * StatusCodeLabel(int code) { switch(code) { case STATUS_SUCCESS: return L"OK" ; case STATUS_WARNING: return L"Warning"; case STATUS_ERROR : return L"Error" ; case STATUS_CRASH : return L"Crash" ; } assert(false); return L"STATUS CODE WRONG!"; } StatusCodes ValidateStatusCode(int code) { switch(code) { case STATUS_SUCCESS: case STATUS_WARNING: case STATUS_ERROR : case STATUS_CRASH : return code; } assert(false); return STATUS_UNKNOWN; } Такой подход наиболее дешев в сопровождении и эффективен в коде, даже когда констант очень много. Почему-то именно про сопровождение обычно забывают. Поставте себя на место программиста который в первый раз читает код с "контейнерным" сопоставлением. Он видит контейнер, константы, строки какие-то и первые мысли у него будут что это часть какой-то прикладной логики, а не простое сопоставление строк константам. Вот так по чуть-чуть накапливается ненужная сложность.

Ответ 4



Это смотря для чего нужно. Иногда из таких констант нужно генерировать enum'ы или функции. Тогда поможет boost.preprocessor. Пример из недавнего проекта: #define SB_PROP_TYPES \ /* type name, type, is pointer, is native, array length */ \ /* note: all non-pointer types should be listed */ \ /* in `sb_config_entry`. */ \ /* here, `$` is a valid pointer to `sb_config_entry`, */ \ /* `$$` is a `v_ptr` casted into a proper type. */ \ ((SB_STRING, char, 1, 1, (strlen($$) + 1) )) \ ((SB_LITERAL, char, 1, 1, (strlen($$) + 1) )) \ ((SB_INT, int, 0, 1, )) \ ((SB_BOOL, bool, 0, 1, )) \ ((SB_CHAR_PAIR_ARR, sb_prop_char_pair_array, 1, 0, (1) )) \ ((__SB_UNKNOWN, int, 0, 1, )) // ... enum sb_prop_types { #define __SB_OP(r, data, elem) (BOOST_PP_TUPLE_ELEM(5, 0, elem)) BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(__SB_OP, , SB_PROP_TYPES)) #undef __SB_OP }; Такая штука развернется в enum sb_prop_types { SB_STRING, SB_LITERAL, SB_INT, SB_BOOL, SB_CHAR_PAIR_ARR, __SB_UNKNOWN }; Далее, есть такой пример — выполняет роль вашего switch (работа со строками, так что используется много ифов, но вообще можно и switch): static inline enum sb_prop_types prop_to_type(const char *__name) { #define __SB_OP(r, data, elem) \ if (strcasecmp(BOOST_PP_TUPLE_ELEM(3, 0, elem), __name) == 0) \ return BOOST_PP_TUPLE_ELEM(3, 2, elem); BOOST_PP_SEQ_FOR_EACH(__SB_OP, _, SB_PROPS) #undef __SB_OP return __SB_UNKNOWN; } Соответственно, развернется в большое количество ифов.

Комментариев нет:

Отправить комментарий