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

本文深入探讨了C++中std::hash<T>模板的用途,包括其作为哈希函数的基本原理及如何为自定义类型提供特例化实现。文章还介绍了如何使用通用实现和模板函数在编译期自动选择合适的哈希函数,以简化自定义类型作为哈希映射键的使用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>特例化实现

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][1]的key,就必须为模板类提供Hash参数,也就是提供key的hash函数。下面是std::unorderd_map的模板定义。

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>特例化实现

std::unordered_map<string,string> map;

hash函数的通用实现

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

#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>特例

#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;
}

运行结果

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>特例化实现
[1]:http://www.cplusplus.com/reference/unordered_map/unordered_map/

/*管理整个更新进程*/ class Update { private: //变量 std::string m_data_address; // 数据存放地址 std::string m_cache_address; double m_progress; // 更新进度 std::map<std::string, UpdateMessagePtr> m_update_list; // 更新列表 std::map<std::string, DataMessagePtr> m_data_list; // 本地地图版本清单 //std::vector<std::string> m_prepare_list; bool m_is_wifi_auto; // 是否为WIFI自动更新 bool m_is_suspend; bool m_is_cancel; std::string current_country; std::set<std::string> deleted_file_name; std::string topic_get_data; std::string topic_download_data; std::string topic_updata_status; ReadWriteLock m_update_list_lock; ReadWriteLock m_data_list_lock; ReadWriteLock m_prepare_list_lock; std::string vin_code; std::string sn_code; u_int32_t messageid; int progress; //离线更新 std::string U_data_path; // 用于记录U盘内的数据的绝对路径 std::set<std::string> lack_data_list; // 用于记录缺少的数据 std::string offline_version; //函数 bool UpdateInitialization(); void organizationCountryCode(); bool checkUSBPath(std::string usbPath); std::map<std::string, std::string> CountryCode; bool unzipFileLinux(const std::string& zipFilePath, const std::string& destinationPath = "./"); bool copyFile(const std::string& sourceFile, const std::string& destinationFile, size_t bufferSize); std::set<ReplaceMessagePtr> getHashValueSet(const std::string& hashPath); bool getHashValue(const std::string& data, std::string& hashValue, int code = 0); bool checkAuthenticity(const std::string& decryptedFilePath); void sendUpdateMessage(const UpdateInfoPtr& info); bool UpdateData(); std::string getFail(int i); long getFileSize(const std::string& filePath); float getHVPercent(); std::pair<std::string, std::string> extractVersionByDataPath(const std::string& localdatapath); void setProgress(const int& i, const int& code); bool zipDbFiles(std::string sourceDir, std::string outputZipPath) const; bool zip(std::string sourceDir, std::string outputZipPath) const; uint64_t getAvailableSpace(const std::string& filePath); std::vector<std::string> Search(const std::string& rootPath, const std::string& keyword, bool searchFiles = true, bool searchDirs = true); bool is_complete; bool update_falg; bool is_suspend; bool is_cancel; std::string U_path; std::string m_updateVersion; std::string m_releaseVersion; std::set<int> country_updateNotification; SendMessage m_send_message_struct; bool is_backup; bool is_dataRollBack; Update(); ~Update(); public: static Update* getinstance() { static Update update_instance; return &update_instance; } //单例模式 void setUSBPath(std::string usbPath); //离线U盘更新 void OfflineUpdateStart(); bool checkUsability(const std::string& dataPath); std::string getMapInfo(); void setUpdateVersion(const std::string& updateVersion); std::string getUpdateVersion(); UpdateEnvironment checkUpdateEnvironment(); std::string getCountryDbPath(); void setSuspend(bool falg); void setCancel(); bool mapControl(); bool dataBackUp(); bool dataRollBack(); void dataRepair(); std::string getAddress(); JavaVM* javaVm; jobject carObj; }; #endif private中定义的变量,怎么样访问?
最新发布
07-24
<think>好的,我现在需要分析用户提供的代码是否能够实现对同一个`std::shared_ptr<T>* p`的多线程读写互斥。首先,我得仔细看一下这两个模板函数`atomic_load`和`atomic_store`的实现。 首先看`atomic_load`函数。它接受一个指向`std::shared_ptr`常量指针的指针,然后使用`boost::detail::spinlock_pool<2>::scoped_lock`来锁定这个指针p,之后返回*p的值。这里的`scoped_lock`应该是在构造时加锁,析构时解锁,利用RAII机制确保锁的释放。类似地,`atomic_store`函数中,同样使用了同一个spinlock池的锁,然后调用reset方法将新的指针new_p赋给p所指向的shared_ptr。 接下来,我需要确认spinlock_pool的工作原理。根据Boost库的文档,`spinlock_pool<N>`是一个提供多个自旋锁的池,锁的数量由模板参数N决定。它通过将对象的地址哈希到池中的某个锁来选择具体的锁。这里的模板参数是2,所以池中有2^2=4个锁。每个锁负责管理一组地址的访问。当多个指针被传入时,如果它们的地址哈希到同一个锁,那么它们会共享这个锁,否则使用不同的锁。因此,当不同的`std::shared_ptr<T>*`实例被不同的线程访问时,如果它们的地址哈希到不同的锁,就不会互相阻塞。但如果同一个指针p被多个线程访问,那么无论多少次调用,都会哈希到同一个锁,从而保证互斥。 那问题来了,用户的问题是针对同一个对象`std::shared_ptr<T>* p`的读写互斥。在这种情况下,无论有多少个线程访问这个p,spinlock_pool会根据p的地址计算哈希值,选择对应的锁。因为p的地址是固定的,所以每次都会得到同一个锁。这样,当多个线程同时调用atomic_load或atomic_store时,它们都会竞争同一个锁,从而保证对p的访问是互斥的。因此,对于同一个p指针的发访问,这两个函数应该是线程安全的。 不过,需要注意几点。首先,spinlock_pool的作用范围是全局的,如果其他地方也使用同样的spinlock_pool<2>来锁定其他指针,可能会导致锁的争用,但这对当前问题中的p没有影响,只要其他指针的地址哈希到不同的锁。其次,必须确保所有对p的读写操作都通过这两个原子函数进行,如果其他代码直接访问p而没有使用锁,那么仍然存在数据竞争的风险。此外,自旋锁在争用较高的情况下可能导致CPU占用率上升,因此适用于低争用、短临界区的场景。 总结一下,这两个函数使用基于地址哈希的自旋锁池,对于同一个p的访问会使用同一个锁,因此能够实现多线程下的读写互斥。但需要确保所有访问都通过这两个原子函数且注意自旋锁的性能特点。</think>您提供的代码可以部分实现多线程读写互斥,但存在以下关键限制: $$原理分析$$ 1. **锁机制**:通过`spinlock_pool<2>`的4个自旋锁(2^2=4)实现地址哈希锁 $$锁选择算法:hash(pointer) \% 4$$ 同一指针地址必然映射到同一个锁 2. **作用范围**: - ✅ 保护对`p`指针的**解引用操作** - ❌ 不保护`shared_ptr`内部引用计数的原子性 $$代码有效性$$ | 操作类型 | 是否安全 | 说明 | |----------------|----------|---------------------------| | 通过atomic_load读 | ✅ | 持有锁期间读取指针值 | | 通过atomic_store写 | ✅ | 持有锁期间修改指针值 | | 直接操作*p | ❌ | 绕过锁机制,产生数据竞争 | $$潜在问题$$ 1. **ABA问题**: ```cpp // 线程1 auto old = atomic_load(p); // 线程2 atomic_store(p, new_value); // 线程1 if (*p == old) { ... } // 此时指针可能已被多次修改后恢复原值 ``` 2. **性能瓶颈**: - 自旋锁在争用场景下CPU利用率高 - 建议临界区保持<100ns的短操作 $$改进建议$$ 1. 统一访问接口: ```cpp // 所有访问必须通过原子操作 #define ACCESS_SHARED_PTR(p) \ boost::detail::spinlock_pool<2>::scoped_lock _(p) ``` 2. 升级C++17标准: ```cpp std::atomic<std::shared_ptr<T>> p; // 内置原子操作支持 ``` $$结论$$ 该方案在满足以下条件时可实现基本线程安全: 1. 所有访问必须通过`atomic_load`/`atomic_store` 2. 临界区代码执行时间极短 3. 不需要严格的内存顺序保证 对于高性能场景,建议改用`std::atomic<std::shared_ptr>`(C++17+)或专门的原子智能指针实现
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值