Меня тут давеча на 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 генерируют точно такой же код, как и для вручную записанных битовых операций.
Ну а теперь полная реализация (ее текущий вариант, не факт, что хороший и окончательный):
namespace details { template< typename T > constexpr T mask( unsigned bits_to_extract ) { return bits_to_extract <= 1u ? T{1} : ((mask<T>(bits_to_extract-1) << 1) | T{1}); } template< typename T > struct bits_count; template<> struct bits_count<std::uint8_t> { static constexpr unsigned count = 8u; }; template<> struct bits_count<std::int8_t> { static constexpr unsigned count = 8u; }; template<> struct bits_count<char> { static constexpr unsigned count = 8u; }; template<> struct bits_count<std::uint16_t> { static constexpr unsigned count = 16u; }; template<> struct bits_count<std::int16_t> { static constexpr unsigned count = 16u; }; template<> struct bits_count<std::uint32_t> { static constexpr unsigned count = 32u; }; template<> struct bits_count<std::int32_t> { static constexpr unsigned count = 32u; }; template<> struct bits_count<std::uint64_t> { static constexpr unsigned count = 64u; }; template<> struct bits_count<std::int64_t> { static constexpr unsigned count = 64u; }; } /* namespace details */ /*! * \brief Extract N bits from a bigger integer value. * * Usage example: * \code * // Extract 8 bits as unsigned char from bits 24..31 in uint32_t. * const std::uint32_t v1 = some_uint_value(); * const auto u8 = n_bits_from<std::uint8_t, 24>(v1); * * // Extract 6 bits as char from bits 12..17 in uint32_t. * const auto ch = n_bits_from<char, 12, 6>(v1); * * // Extract 4 bits as unsigned int from bits 32..35 in uint64_t. * const std::uint64_t v2 = some_uint64_value(); * const auto ui = n_bits_from<unsigned int, 32, 4>(v2); * \endcode * */ 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); } |
Комментариев нет:
Отправить комментарий