C++17自定义二进制格式处理:两种选择器的优化方案问询
针对C++17二进制格式选择器的通用解决方案
针对你遇到的两种二进制选择器场景(1位二选一、x位多选一),可以通过编译期绑定位模式与std::variant类型索引+封装位操作与读写逻辑的方式,实现更健壮、可复用的通用方案,避免手动映射的错误和冗余代码。
核心思路
- 用模板特化定义编译期映射表,将每个结构体对应的位选择器值与variant的类型索引绑定;
- 封装
bit_reader/bit_writer工具类,统一处理二进制流中的位读取/写入,解决位对齐问题; - 基于映射表和位工具类,封装通用的variant读写函数,实现逻辑复用。
代码实现
1. 位操作工具类
先实现简化版的位读写工具,处理流中的位打包/解包:
#include <istream> #include <ostream> #include <cstdint> #include <stdexcept> class bit_reader { public: explicit bit_reader(std::istream& is) : is_(is), current_byte_(0), bit_pos_(8) {} uint64_t read_bits(size_t count) { if (count > 64) throw std::invalid_argument("Too many bits to read"); uint64_t result = 0; while (count > 0) { if (bit_pos_ == 8) { if (!is_.read(reinterpret_cast<char*>(¤t_byte_), 1)) { throw std::runtime_error("Unexpected end of stream"); } bit_pos_ = 0; } size_t take = std::min(count, 8 - bit_pos_); result |= (static_cast<uint64_t>(current_byte_ >> (8 - bit_pos_ - take)) & ((1ULL << take) - 1)) << (count - take); bit_pos_ += take; count -= take; } return result; } // 暴露底层字节流(如果结构体读取需要字节对齐) std::istream& underlying_stream() { return is_; } private: std::istream& is_; uint8_t current_byte_; size_t bit_pos_; }; class bit_writer { public: explicit bit_writer(std::ostream& os) : os_(os), current_byte_(0), bit_pos_(0) {} ~bit_writer() { if (bit_pos_ > 0) { os_.write(reinterpret_cast<char*>(¤t_byte_), 1); } } void write_bits(uint64_t value, size_t count) { if (count > 64) throw std::invalid_argument("Too many bits to write"); while (count > 0) { size_t take = std::min(count, 8 - bit_pos_); current_byte_ |= (static_cast<uint8_t>(value >> (count - take)) & ((1U << take) - 1)) << (8 - bit_pos_ - take); bit_pos_ += take; count -= take; if (bit_pos_ == 8) { os_.write(reinterpret_cast<char*>(¤t_byte_), 1); current_byte_ = 0; bit_pos_ = 0; } } } // 暴露底层字节流 std::ostream& underlying_stream() { return os_; } private: std::ostream& os_; uint8_t current_byte_; size_t bit_pos_; };
2. 编译期选择器映射表
通过模板特化,将variant的类型与对应的位选择器值绑定,支持连续/离散的位模式:
#include <variant> #include <array> #include <utility> template<typename Variant> struct SelectorMapping; // 示例1:1位选择器(Struct1对应0,Struct2对应1) struct Struct1 { /* ... */ }; struct Struct2 { /* ... */ }; template<> struct SelectorMapping<std::variant<Struct1, Struct2>> { static constexpr size_t bit_count = 1; static constexpr std::array<uint64_t, std::variant_size_v<std::variant<Struct1, Struct2>>> values = {0, 1}; static size_t index_from_value(uint64_t val) { // 连续值直接返回,无需查找 if (val >= values.size()) throw std::runtime_error("Invalid selector value"); return static_cast<size_t>(val); } }; // 示例2:3位选择器(离散位模式) struct StructA { /* ... */ }; struct StructB { /* ... */ }; struct StructC { /* ... */ }; struct StructD { /* ... */ }; struct StructE { /* ... */ }; struct StructF { /* ... */ }; struct StructG { /* ... */ }; struct StructH { /* ... */ }; template<> struct SelectorMapping<std::variant<StructA, StructB, StructC, StructD, StructE, StructF, StructG, StructH>> { static constexpr size_t bit_count = 3; static constexpr std::array<uint64_t, 8> values = {5, 2, 7, 1, 0, 3, 6, 4}; // 自定义离散位模式 static size_t index_from_value(uint64_t val) { // 编译期生成查找逻辑,避免运行时std::find开销 size_t idx = static_cast<size_t>(-1); ((val == values[0] ? idx = 0 : void()), (val == values[1] ? idx = 1 : void()), (val == values[2] ? idx = 2 : void()), (val == values[3] ? idx = 3 : void()), (val == values[4] ? idx = 4 : void()), (val == values[5] ? idx = 5 : void()), (val == values[6] ? idx = 6 : void()), (val == values[7] ? idx = 7 : void())); if (idx == static_cast<size_t>(-1)) throw std::runtime_error("Invalid selector value"); return idx; } }; // 编译期检查映射表有效性(可选) template<typename Variant> constexpr bool is_mapping_valid() { const auto& vals = SelectorMapping<Variant>::values; // 检查映射表大小与variant类型数量一致 if (vals.size() != std::variant_size_v<Variant>) return false; // 检查位值无重复 for (size_t i = 0; i < vals.size(); ++i) { for (size_t j = i+1; j < vals.size(); ++j) { if (vals[i] == vals[j]) return false; } } return true; } // 对示例1做编译期验证 static_assert(is_mapping_valid<std::variant<Struct1, Struct2>>(), "Invalid mapping for Struct1/Struct2 variant");
3. 通用Variant读写函数
基于映射表和位工具类,封装可复用的读写逻辑:
// 假设每个结构体都有对应的read/write函数,比如: // void read(Struct1& obj, std::istream& is); // void write(const Struct1& obj, std::ostream& os); // ... 其他结构体同理 template<typename Variant> Variant read_variant(bit_reader& reader) { using Mapping = SelectorMapping<Variant>; static_assert(is_mapping_valid<Variant>(), "Invalid selector mapping"); uint64_t selector_val = reader.read_bits(Mapping::bit_count); size_t idx = Mapping::index_from_value(selector_val); Variant var; // 构造对应类型并读取数据 std::visit([&](auto&& arg) { using T = std::decay_t<decltype(arg)>; T obj; read(obj, reader.underlying_stream()); var = std::move(obj); }, var.emplace(idx)); return var; } template<typename Variant> void write_variant(bit_writer& writer, const Variant& var) { using Mapping = SelectorMapping<Variant>; static_assert(is_mapping_valid<Variant>(), "Invalid selector mapping"); size_t idx = var.index(); writer.write_bits(Mapping::values[idx], Mapping::bit_count); // 写入对应结构体数据 std::visit([&](const auto& obj) { write(obj, writer.underlying_stream()); }, var); }
方案优势
- 编译期安全:通过static_assert检查映射表的有效性,避免手动映射的错误;
- 高复用性:新增结构体只需扩展variant和映射表,无需修改读写逻辑;
- 高效性:连续位模式直接返回索引,离散模式用编译期生成的判断替代运行时查找;
- 灵活性:支持任意位数的选择器和离散/连续的位模式,适配不同自定义格式需求。
内容的提问来源于stack exchange,提问作者Decaf Sux
相关产品推荐
相关产品推荐

