前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11 元编程 判断是否有std::hash特例并提供hash函数通用实现

C++11 元编程 判断是否有std::hash特例并提供hash函数通用实现

作者头像
10km
发布2019-05-25 22:43:21
3.9K0
发布2019-05-25 22:43:21
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433797

std::hash<T>的用途

std::hash<T>是C++11提供的一元函数模板,用于向标准库提供返回数据类型T哈希值(hash value)的哈希函数(hash function)。

std::hash<T>只是定义了一个一元操作符operator(),接受一个T类型的参数,返回一个size_t类型的哈希值,

C++11为所有基本类型(basic types)都提供了特例化实现:

C++11标准库定义的类型也提供都有提供特例化实现:

自定义类型的std::hash<T>特化

但是自定义的类型需要程序员自己定义std::hash<T>的特例化实现

比如下面代码就为自定义类型struct S提供 了std::hash<S>特例化实现

代码语言:javascript
复制
struct S
{
    std::string first_name;
    std::string last_name;
};
/* 为S提供 std::hash<T>特例化实现 */
namespace std
{
    template<>
    struct hash<S>
    {
        typedef S argument_type;
        typedef std::size_t result_type;

        result_type operator()(argument_type const& s) const
        {
            result_type const h1 ( std::hash<std::string>()(s.first_name) );
            result_type const h2 ( std::hash<std::string>()(s.last_name) );
            return h1 ^ (h2 << 1);
        }
    };

}

为自定义类型提供std::hash<T>特例化有什么用呢?

比如,如果你要使用上面的自定义类型struct S作为std::unorderd_map的key,就必须为模板类提供Hash参数,也就是提供key的hash函数。下面是std::unorderd_map的模板定义。

代码语言:javascript
复制
template < class Key,                                    // unordered_map::key_type
           class T,                                      // unordered_map::mapped_type
           class Hash = hash<Key>,                       // unordered_map::hasher
           class Pred = equal_to<Key>,                   // unordered_map::key_equal
           class Alloc = allocator< pair<const Key,T> >  // unordered_map::allocator_type
           > class unordered_map;

我们一般像下面这样使用unordered_map,不用提供Hash 参数,是因为对于string,STL已经提供了stringstd::hash<T>特例化实现

代码语言:javascript
复制
std::unordered_map<string,string> map;

hash函数的通用实现

有时在项目中有多个自定义类型需要提供std::hash<T>特例化实现,为每个类型写一个特例化实现也挺烦的。

那么可以考虑提供一个hash函数的通用实现,并在编译期通过模板函数自动判断类型是否有std::hash<T>的特例实现,如果有就使用T自己的特例化实现,如果没有就使用通用的hash函数实现,下面是实现代码,详细说明见代码中注释:

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <string>
#include <type_traits>
#include <unordered_map>
using namespace std;
/* TT没有std::hash<TT>特例化实现 */
struct TT{
const int t1=18354;
};

struct S
{
    std::string first_name;
    std::string last_name;
};
/* 为S提供 std::hash<T>特例化实现 */
namespace std
{
    template<>
    struct hash<S>
    {
        typedef S argument_type;
        typedef std::size_t result_type;

        result_type operator()(argument_type const& s) const
        {
            result_type const h1 ( std::hash<std::string>()(s.first_name) );
            result_type const h2 ( std::hash<std::string>()(s.last_name) );
            return h1 ^ (h2 << 1);
        }
    };

}
/* 返回获取hash值的一元函数实现,
 * 如果T有std::hash<T>特例实现返回std::hash<T>,否则提供缺省的hash实现
 */
template<typename T>
struct hash_fn{
    /* 缺省的hash实现 */
    struct default_hash {
        typedef T argument_type;
        typedef std::size_t result_type;
        result_type operator()(argument_type const& t) const noexcept {
                auto obj_size=sizeof(t);
                uint64_t hashcode = 0;
                uint64_t *p = (uint64_t*) std::addressof(t);
                uint64_t *const u = p + obj_size/sizeof(uint64_t);
                const int tail=obj_size % sizeof(uint64_t);
                const int shift = (sizeof(uint64_t) - sizeof(uint32_t)) << 3;
                //BKDRHash
                uint64_t seed = 131; // 31 131 1313 13131 131313 etc..
                for (; p < u; p ++) hashcode = hashcode * seed + *p;
                if (tail)
            #if defined( _MSC_VER) || (defined(__GNUC__) && __BYTE_ORDER__ ==__ORDER_LITTLE_ENDIAN__)
                    hashcode = hashcode * seed+ ((*p) &((1ULL<<(tail << 3))-1));// 小端模式
            #elif defined(__GNUC__) && __BYTE_ORDER__ ==__ORDER_BIG_ENDIAN__
                    hashcode = hashcode * seed+ ((*p) >> ((sizeof(uint64_t) - tail) << 3)); // 大端模式
            #else
            #error unexpected c complier (msc/gcc)
            #endif
                return (result_type) hashcode;
            }
    };
    /* SFINAE 判断T有没有std::hash<T>特例实现 */
    template<typename U> static std::hash<U> test(decltype(declval<std::hash<U>>().operator()(declval<U>())));
    template<typename U> static default_hash test(...);
    //如果T没有std::hash<T>特例化实现,则type的类型为default_hash 
    //否则type类型为std::hash<T>
    using type =decltype(test<T>(0));
    type fn;
};

int main()
{
    S s;
    s.first_name = "Bender";
    s.last_name =  "Rodriguez";
    cout<<hash_fn<TT>().fn(TT())<<endl;
    cout<<hash_fn<S>().fn(s)<<endl;
    //S有std::hash<S>特例实现,无需指定std::unordered_map的Hash参数
    std::unordered_map<S,string> map_s;
    //TT没有std::hash<TT>实现,将hash_fn<TT>的计算结果作为Hash参数,
    //hash_fn<TT>::type会自动选择缺省的哈希实现
    std::unordered_map<TT,string,typename hash_fn<TT>::type> map_tt;
}

判断std::hash<T>是否实现的元函数

另外,还可以单独写一个元函数来判断类型T是否有std::hash<T>特例

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <string>
#include <type_traits>

/* 判断有没有std::hash<T>实现 */
template <typename T>
struct has_hash_specific{
    template<typename U> static auto test(int)->    decltype(declval<std::hash<U>>().operator()(declval<U>()));
    template<typename U> static void test(...);
    enum{value=!std::is_void<decltype(test<T>(0))>::value};
    //通过判断test<T>(0)返回值是否为void来判断是否有hash<T>特例
};
struct TT{
const int t1=18354;
};

struct S
{
    std::string first_name;
    std::string last_name;
};

namespace std
{
    template<>
    struct hash<S>
    {
        typedef S argument_type;
        typedef std::size_t result_type;

        result_type operator()(argument_type const& s) const
        {
            result_type const h1 ( std::hash<std::string>()(s.first_name) );
            result_type const h2 ( std::hash<std::string>()(s.last_name) );
            return h1 ^ (h2 << 1);
        }
    };

}
int main()
{
    S s;
    s.first_name = "Bender";
    s.last_name =  "Rodriguez";
    cout<<"has_hash_spec<S>"<<has_hash_specific<S>::value<<endl;
    cout<<"has_hash_spec<int>"<<has_hash_specific<int>::value<<endl;
    cout<<"has_hash_spec<uint64_t>"<<has_hash_specific<uint64_t>::value<<endl;
    cout<<"has_hash_spec<TT>"<<has_hash_specific<TT>::value<<endl;
}

运行结果

代码语言:javascript
复制
has_hash_spec<S>1
has_hash_spec<int>1
has_hash_spec<uint64_t>1
has_hash_spec<TT>0

注意:

default_hash其实只能用于成员为基本类型的class/union/struct,对于包含复杂对象的类型还是需要提供std::hash<T>特例化实现

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015年12月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • std::hash<T>的用途
  • 自定义类型的std::hash<T>特化
  • hash函数的通用实现
  • 判断std::hash<T>是否实现的元函数
  • 注意:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档