如何原地初始化std::array<std::pair<const Key, Val>>以支持不可拷贝类型?
适配不可拷贝类型的枚举键静态映射实现
想要创建一个键为枚举类型的静态映射,将键值对存储在
std::array中。以下代码对可平凡拷贝类型能正常工作,但无法支持std::mutex、std::atomic<T>这类不可拷贝类型,请问是否有适配这类类型的实现版本?猜测可以使用placement new,但或许存在更优雅的基于index_sequence的解决方案。
原代码:
#pragma once #include <magic_enum.hpp> #include <bitset> #include <array> template <typename Key, typename T> struct EnumMap { static_assert(std::is_enum<Key>::value); using key_type = Key; using mapped_type = T; using value_type = std::pair<const key_type, mapped_type>; using size_type = std::size_t; EnumMap() : data_(initializeData()){ } private: static constexpr size_type enum_size_ = magic_enum::enum_count<Key>(); std::array<value_type, enum_size_> data_; std::bitset<enum_size_> filled_; constexpr auto initializeData() { return initializeDataHelper(std::make_index_sequence<enum_size_>()); } template <size_type... Is> constexpr auto initializeDataHelper(std::index_sequence<Is...>) { constexpr auto enum_entries = magic_enum::enum_values<Key>(); return std::array<value_type, enum_size_>{{std::make_pair(enum_entries[Is], T{})... }}; } };
适配不可拷贝类型的实现方案
核心思路是避免对不可拷贝类型的拷贝操作,直接在std::array的内存位置构造每个键值对元素,结合index_sequence遍历枚举索引完成初始化,同时处理手动析构逻辑:
#pragma once #include <magic_enum.hpp> #include <bitset> #include <array> #include <tuple> #include <utility> template <typename Key, typename T> struct EnumMap { static_assert(std::is_enum_v<Key>); using key_type = Key; using mapped_type = T; using value_type = std::pair<const key_type, mapped_type>; using size_type = std::size_t; EnumMap() { initializeData(std::make_index_sequence<enum_size_>()); } // 根据枚举键访问对应值 T& at(Key key) { const auto idx = magic_enum::enum_index(key).value(); return std::get<1>(data_[idx]); } const T& at(Key key) const { const auto idx = magic_enum::enum_index(key).value(); return std::get<1>(data_[idx]); } // 简化访问的operator[] T& operator[](Key key) { return at(key); } const T& operator[](Key key) const { return at(key); } ~EnumMap() { destructData(std::make_index_sequence<enum_size_>()); } // 禁用拷贝/移动,因为存储的T可能不可拷贝/移动 EnumMap(const EnumMap&) = delete; EnumMap& operator=(const EnumMap&) = delete; EnumMap(EnumMap&&) = delete; EnumMap& operator=(EnumMap&&) = delete; private: static constexpr size_type enum_size_ = magic_enum::enum_count<Key>(); std::array<value_type, enum_size_> data_; std::bitset<enum_size_> filled_; template <size_type... Is> void initializeData(std::index_sequence<Is...>) { constexpr auto enum_entries = magic_enum::enum_values<Key>(); // 展开索引序列,逐个构造元素 (constructElement(Is, enum_entries[Is]), ...); } void constructElement(size_type idx, Key key) { // 使用placement new直接在array的内存位置构造pair new (&data_[idx]) value_type( std::piecewise_construct, std::forward_as_tuple(key), // 构造const Key成员 std::forward_as_tuple() // 构造T成员(默认构造) ); filled_.set(idx); } template <size_type... Is> void destructData(std::index_sequence<Is...>) { // 展开索引序列,逐个析构已构造的元素 (destructElement(Is), ...); } void destructElement(size_type idx) { if (filled_.test(idx)) { data_[idx].~value_type(); } } };
关键改动说明
- 避免拷贝操作:不再通过预构造
std::array并拷贝的方式初始化,而是用placement new直接在data_的内存位置构造每个value_type对象,彻底绕过拷贝要求。 - 分段构造pair:使用
std::piecewise_construct分别构造pair的const键和不可拷贝值成员,确保构造过程合法。 - 手动析构管理:因为用
placement new构造的对象不会自动触发析构,所以需要结合filled_标记,在析构函数中手动调用每个元素的析构函数。 - 禁用拷贝/移动:由于存储的
T可能是不可拷贝或移动的,直接禁用整个EnumMap的拷贝和移动语义,避免潜在的未定义行为。 - 添加访问接口:提供
at()和operator[]方法,方便通过枚举键直接访问对应的值。
内容的提问来源于stack exchange,提问作者valikund
相关产品推荐
相关产品推荐

