【C++】枚举类型作为map/unordered_map的key键

C++中mapunordered_map提供的是一种键值对容器,在实际开发中会经常用到,它跟Python的字典很类似,所有的数据都是成对出现的,每一对中的第一个值称之为关键字(key),每个关键字只能在map中出现一次;第二个称之为该关键字的对应值(value)。

那么枚举类型(enum)可以作为键值对容器的key么


问题引出

自定义一个枚举类型,并将其分别作为map和unordered_map的key:

#include <map>
#include <unordered_map>
#include <string>

typedef enum {
  COLOR_INIT = 0,
  COLOR_RED = 1,
  COLOR_BLUE = 2,
  COLOR_GREEN = 3,
  COLOR_YELLOW = 4
} Color;

int main()
{
  std::map<Color, std::string> color_map;
  std::unordered_map<Color, std::string> color_unordered_map;

  return 0;
}

C++11的编译环境下,编译过程中,就会出错:

In file included from main.cpp:1:
In file included from /root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/map:532:
In file included from /root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/__debug:15:
/root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/type_traits:1773:38: error: implicit instantiation of undefined template 'std::hash<Color>'
    : public integral_constant<bool, __is_empty(_Tp)> {};
                                     ^
/root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/unordered_map:539:18: note: in instantiation of template class 'std::is_empty<std::hash<Color>>' requested here
          bool = is_empty<_Hash>::value && !__libcpp_is_final<_Hash>::value>
                 ^
/root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/unordered_map:1026:13: note: in instantiation of default argument for '__unordered_map_hasher<Color, std::__hash_value_type<Color, std::string>, std::hash<Color>, std::equal_to<Color>>' required here
    typedef __unordered_map_hasher<key_type, __value_type, hasher, key_equal> __hasher;
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:16:42: note: in instantiation of template class 'std::unordered_map<Color, std::string>' requested here
  std::unordered_map<Color, std::string> color_unordered_map;
                                         ^
/root/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1/__memory/shared_ptr.h:1552:50: note: template is declared here
template <class _Tp> struct _LIBCPP_TEMPLATE_VIS hash;
                                                 ^
1 error generated.

从编译结果可以很明显地看出:enum可以直接作为map的key,却不能直接作为unordered_map的key

不同编译器可能展示出来的出错log不同,但是里面都会有一个出现频率特别高的词——hash。也就是说,enum不能直接作为unordered_map的key肯定和hash有着很密切的关系。


原因分析

map和unordered_map的不同编译结果,主要体现在其内部构造的不同:

map的内部构造:

map的内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的。红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。

unordered_map的内部构造:

unordered_map的内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的

从内部构造上可以看出:

unordered_mapmap
查找快,Average O(1),worst O(n)恒定log(n)
插入快,Average O(1),worst O(n)log(n)+平衡二叉树调整时间
删除快,Average O(1),worst O(n)log(n)+平衡二叉树调整时间
适用于查找频率高要求有序

因此,可以看出对于unordered_map使用哈希表,因此在自定义的数据结构作为key的时候,需要指定其hash计算方式。不然就会出问题。


问题解决

定义模板对象来计算枚举的哈希值,同时作为unordered_map的第三个参数传入:

#include <unordered_map>
#include <string>
#include <iostream>

typedef enum {
  COLOR_INIT = 0,
  COLOR_RED = 1,
  COLOR_BLUE = 2,
  COLOR_GREEN = 3,
  COLOR_YELLOW = 4
} Color;

struct EnumClassHash
{
    template <typename T>
    std::size_t operator()(T t) const
    {
        return static_cast<std::size_t>(t);
    }
};

int main()
{
  std::unordered_map<Color, std::string, EnumClassHash> color_unordered_map;

  return 0;
}

需要注意的是:无需提供std::hash<>的特殊化,模板参数推导即可完成

当然,可以使用单词using并根据key是否为枚举类型,从而选择使用EnumClassHash还是std::hash,从而定义一个新的MyUnorderedMap:

template <typename T>
using HashType = typename std::conditional<
    std::is_enum<T>::value, EnumClassHash, std::hash<T> >::type;

template <typename Key, typename T>
using MyUnorderedMap = std::unordered_map<Key, T, HashType<Key> >;

这里,通过std::conditional进行条件判断,如果T是enum枚举类型,就是EnumClassHash,否则为默认的std::hash

这样的话,直接使用MyUnorderedMap,不用担心key是不是枚举类型了:

MyUnorderedMap<Color, std::string> color_unordered_map;

最最最重要的是:

这被认为是标准中的缺陷,并且已在C++14中得到修复C++ Standard Library Defect Reports and Accepted Issues (Revision R124)

如果你是C++14及其以上的版本,那么就不会有这个问题了。


相关阅读

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值