37、如何自定义 `std::unordered_set、std::unordered_map` 中元素的哈希函数和相等函数?(附特化)

如何自定义 `std::unordered_set、std::unordered_map` 中元素的哈希函数和相等函数

方法 1:定义独立的哈希函数对象

  1. 自定义类型
    假设需要存储包含nameage的结构体Person

    struct Person {
        std::string name;
        int age;
    };
    
  2. 定义独立的哈希函数对象(仿函数)
    需要定义一个结构体 PersonHash,它覆盖了自定义的哈希函数即 重载operator(),返回size_t类型:

    struct PersonHash {
        size_t operator()(const Person& p) const {
            // 组合 name 的哈希值和 age 的哈希值
            return std::hash<std::string>()(p.name) ^ (std::hash<int>()(p.age) << 1);
        }
    };
    

    注意:实际中应使用更可靠的哈希组合方式(如boost::hash_combine)。

  3. 自定义相等比较函数(仿函数)
    定义一个结构体PersonEqual,它覆盖了自定义的相等比较函数即 重载operator(),返回bool类型:

    struct PersonEqual {
        bool operator()(const Person& a, const Person& b) const {
            return a.name == b.name && a.age == b.age;
        }
    };
    //也可以放在自定义类型中,这样就不用写在unordered_set的初始化语句中了
    
  4. 实例化 unordered_set
    模板参数中指定PersonHashPersonEqual

    std::unordered_set<Person, PersonHash, PersonEqual> person_set;
    

完整示例

#include <iostream>
#include <unordered_set>
#include <string>

struct Person {
    std::string name;
    int age;
};

// 自定义哈希函数
struct PersonHash {
    size_t operator()(const Person& p) const {
        return std::hash<std::string>{}(p.name) ^ (std::hash<int>{}(p.age) << 1);
    }
};

// 自定义相等比较函数
struct PersonEqual {
    bool operator()(const Person& a, const Person& b) const {
        return a.name == b.name && a.age == b.age;
    }
};

int main() {
    std::unordered_set<Person, PersonHash, PersonEqual> person_set;//显式指定哈希函数
    
    person_set.insert({"Alice", 30});
    person_set.insert({"Bob", 25});
    
    // 检查是否存在 Alice, 30
    if (person_set.find({"Alice", 30}) != person_set.end()) {
        std::cout << "Found Alice!" << std::endl;
    }
    return 0;
}
/*
如果把相等比较函数放入结构体person中,则会变成这样:
struct Person {
    std::string name;
    int age;
    
    bool operator()(const Person& a, const Person& b) const {
        return a.name == b.name && a.age == b.age;
    }
};

//此时在定义时就可以少写一个结构体了
std::unordered_set<Person, PersonHash> person_set;
*/

方法 2:特化 std::hash 模板

步骤

  1. 定义自定义类型(如 Person 类)
  2. namespace std 中特化 std::hash
  3. 实现 operator() 函数,组合各成员变量的哈希值
  4. 提供 operator== 重载(哈希容器需要)

示例代码

#include <string>
#include <functional>

// 自定义类型
struct Person {
    std::string name;
    int age;
    // 必须提供相等运算符
    bool operator==(const Person& other) const {
        return name == other.name && age == other.age;
    }
};

// 特化 std::hash
namespace std {
    template<>
    struct hash<Person> {
        size_t operator()(const Person& p) const {
            // 组合 name 和 age 的哈希值
            size_t name_hash = hash<string>{}(p.name);
            size_t age_hash = hash<int>{}(p.age);
            // 使用异或组合哈希(可改进为更复杂的组合方式)
            return name_hash ^ (age_hash << 1);
        }
    };
}

// 使用示例
#include <unordered_set>
int main() {
    std::unordered_set<Person> person_set; // 自动使用特化的 std::hash<Person>
    return 0;
}

关键注意事项
  1. 哈希组合技巧:简单异或(^)容易导致碰撞,建议使用类似 hash_combine 的算法:
    template <class T>
    inline void hash_combine(std::size_t& seed, const T& v) {
        seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    
  2. 哈希质量:确保不同对象的哈希值尽可能唯一,否则会影响容器性能
  3. 命名空间限制特化【注】 std::hash 必须在 namespace std 中。由于
    std::hash原生定义在namespace std中,任何针对它的显式特化(如std::hash)也必须置于该命名空间,否则编译器将视为未定义

其他实现方式

  1. 使用 Lambda 表达式(需 C++20)
    通过构造函数传递哈希和比较函数:
   auto hash = [](const Person& p) { /*...*/ };
   auto equal = [](const Person& a, const Person& b) { /*...*/ };
   std::unordered_set<Person, decltype(hash), decltype(equal)> set(10, hash, equal);

注:特化(Specialization) 和实例化是不一样的,实例化是把模板赋予具体的类型了,而特化仍然是一个模板,只不过是一个特殊的模板。也就是说,特化之后也会被实例化(而且比通用的模板更优先被匹配)。所以,特化的意义就在于:它可以为特定的实例类型 提供定制化的实现(不同于其他的同样模板)

  1. 模板特化
    在C++等支持泛型编程的语言中,特化指为泛型模板提供特定类型或参数的定制化实现。例如:

    // 通用模板
    template <typename T>
    class MyContainer { /*...*/ };
    
    // 全特化:针对int类型的完全定制实现
    template <>
    class MyContainer<int> { /*...*/ };
    

    这里template <>标识了全特化,即完全覆盖通用模板的行为。

  2. 部分特化(偏特化)
    与全特化不同,部分特化允许仅对模板参数的部分特性进行定制。例如:

    // 通用模板
    template <typename T, typename U>
    class Pair { /*...*/ };
    
    // 偏特化(2类)
    //1.指定部分参数
    template <typename T>
    class Pair<T, T> { /*...*/ };
    
    //2.通过模式匹配约束参数类型
    template <class T> class vector<T*> { ... };  // 对指针类型的偏特化
    //这里的T*表示所有指针类型,未完全固定参数,但通过模式限定了参数的形式。
    

特化的意义

  • 性能优化:为特定数据类型(如int)提供高度优化的实现,避免泛型代码的运行时开销。
  • 功能扩展:针对特殊需求调整通用逻辑,例如为指针类型添加内存管理逻辑。
  • 约束行为:限制某些类型的使用方式,如禁止拷贝语义的类型。

与其他概念的对比

  • 多态:特化属于编译时多态,而虚函数实现的是运行时多态。
  • 重载:特化修改了模板本身的行为,而函数重载通过参数列表区分不同实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值