Меня тут давеча на LOR-е типа обвинили в том, что боюсь и шаблонов, и возни с битами и байтами. Юмор этой ситуации заключался в том, что как раз незадолго до этого мы у себя в RESTinio по мере подготовке к релизу очередной публичной версии как раз проводили рефакторинг операций над битами и байтами.
Дело в том, что в коде RESTinio со временем появился ряд операций, в которых требовалось извлечь сколько-то битов из какого-то значения. В принципе, это все элементарные вещи вроде (bs >> 18) & 0x3f. Однако, когда таких элементарных вещей нужно записать несколько штук подряд, да еще в разных местах, да еще с преобразованием результата к разным типам, то лично у меня в голове начинает звучать тревожный звоночек: слишком много хардкодинга и копипасты. А поскольку по поводу копипасты и ее опасности у меня есть собственный пунктик, то в итоге операции с битами и байтами мы упрятали во вспомогательную шаблонную функцию. Там, где у нас было что-то подобное:
result.push_back( alphabet_char( static_cast<char>((bs >> 18) & 0x3f) ) ); result.push_back( alphabet_char( static_cast<char>((bs >> 12) & 0x3f) ) ); result.push_back( alphabet_char( static_cast<char>((bs >> 6) & 0x3f) ) ); result.push_back( alphabet_char( static_cast<char>(bs & 0x3f) ) ); |
появилось что-то вот такое:
template<unsigned int SHIFT> char sixbits_char( uint_type_t bs ) { return ::restinio::impl::bitops::n_bits_from< char, SHIFT, 6 >(bs); } ... result.push_back( alphabet_char( sixbits_char<18>(bs) ) ); result.push_back( alphabet_char( sixbits_char<12>(bs) ) ); result.push_back( alphabet_char( sixbits_char<6>(bs) ) ); result.push_back( alphabet_char( sixbits_char<0>(bs) ) ); |
Где ключевую роль играет тривиальная шаблонная функция n_bits_from:
template< typename T, unsigned SHIFT, unsigned BITS_TO_EXTRACT = details::bits_count<T>::count, typename F = unsigned int > T n_bits_from( F value ) { return static_cast<T>(value >> SHIFT) & details::mask<T>(BITS_TO_EXTRACT); } |
Полная реализация n_bits_from в ее текущем виде находится под катом. Но сказать хочется вот что: возможно, это уже оверкилл и злоупотребление шаблонами. Но, как мне кажется, использование n_bits_from в местах, где операции по извлечению битов из байтов производятся в большом количестве, помогает избежать:
- во-первых, замыливания глаза при повторении однотипных операций. Когда приходится записывать подряд штук 5-6 сдвигов с последующими "логическими И", то очень легко где-то ошибиться и записать не то смещение или не ту битовую маску. Такие ошибки, к сожалению, не так просто заметить и они могут жить в коде очень долго, особенно, если код недостаточно покрыт тестами;
- во-вторых, неявных приведений типов, которые в C++ могут приводить к неожиданным результатам. Например, можно легко попытаться получить из char-а значение unsigned int, забыв про промежуточный каст в unsigned char. И, если в char-е установлен старший бит, то получить нежданчик. Особенно это круто в ситуации, когда сперва 8 бит извлекаются из int-а в char, а затем этот char используется в качестве индекса в массиве (т.е. может произойти расширение из char в size_t, который беззнаковый).
Так что, может я совсем упорот шаблонами, но как по мне, если сдвиги и "логические И" пошли в коде косяком, то лучше обезопасить себя за счет похожих на n_bits_from вспомогательных функций. Тем более, что оптимизирующие компиляторы для n_bits_from генерируют точно такой же код, как и для вручную записанных битовых операций.
Ну а теперь полная реализация (ее текущий вариант, не факт, что хороший и окончательный):