当前位置: 首页 > 知识库问答 >
问题:

只要我能为我的类型提供hasher函数,为什么我需要为我的类型专门设置std::hash?

高展
2023-03-14

我已经实现了这个示例:

class Foo;
template<>
class std::hash<Foo>;

class Foo
{
public:
    Foo(std::string const& s, int x) : str_(s), x_(x){}
    std::string str()const {return str_;}
    int get_x() const{ return x_;}

private:
    std::string str_;
    int x_{};
    friend bool operator==(Foo const&, Foo const&);
    friend class std::hash<Foo>;
};

bool operator==(Foo const& lhs, Foo const& rhs)
{
    return lhs.str_ == rhs.str_ && lhs.x_ == rhs.x_;
}

size_t hasher(Foo const& f)
{
    return std::hash<std::string>()(f.str()) ^
            std::hash<int>()(f.get_x());
}

namespace std
{
    template <>
    class hash<Foo>
    {
    public:
        using argument_type = Foo;
        using result_type = std::size_t;
        result_type operator()(argument_type const& a)const;
    };

    typename hash<Foo>::result_type
    hash<Foo>::operator()(argument_type const& a)const
    {
        return hash<std::string>()(a.str_) ^
                hash<int>()(a.x_);
    }
}

int main()
{

    std::unordered_set<Foo, decltype(hasher)*> u(5, hasher); // needs hasher as template argument and as function argument
    u.insert(Foo{"hi", 100});

    std::unordered_set<Foo> u2; // doesn't need either
    u2.insert(Foo("hi", 100));

    std::cout << "\ndone!\n";
}

>

  • 如果我运行程序,程序编译和工作都很好。正如您所看到的,我的hasher函数与调用运算符中的功能相同,它是std::hash 专门化的成员函数,那么为什么我专门化后一个std::hash而可以提供简单的hasher函数呢?

    那是为了灵活性吗?例如,为我的foo类型提供hasher函数而不是专门化std::hash类型,需要在关联容器实例化中指定为模板参数和函数参数,比如unordered_set,而后者不需要?

  • 共有1个答案

    燕凯旋
    2023-03-14

    只要我可以为我的类类型提供hasher,为什么我[我需要]专门化std::hash

    你不需要。无论如何,这里有一些这样做的理由:

    >

  • 如果您的是API的一部分(内部或外部都无关紧要),并且希望为的用户提供将其用作无序容器中的键的可能性(或者出于任何其他原因,对其进行哈希),友好的方法是专门化std::hash。没有人会期望必须指定一个特殊的函数来为可哈希的类型(可能被记录为)进行哈希。

    一旦编写了专门化,就不需要免费的哈希函数了,所以如果上面的任何一个似乎适用,就可以立即专门化std::hash(并完全跳过免费哈希函数)。

    使样板更短的一种方法是使用struct而不是class对其进行专门化。主类模板是

    namespace std {
        template<class Key>
        struct hash;
    }
    

    所以这样做也是完全有意义的。您的定义是:

    namespace std {
        struct hash<Foo> {
            using argument_type = Foo;
            using result_type = std::size_t;
            result_type operator()(argument_type const& a) const;
        };
    }
    

  •  类似资料:
    • 问题内容: 具体来说,在关系数据库管理系统中,为什么我们在创建时需要知道列的数据类型(更可能是对象的属性)? 对我来说,数据类型感觉就像是一种优化,因为可以以多种方式实现一个数据点。将语义角色和约束分配给数据点,然后让引擎在内部检查和优化哪种数据类型最能为用户服务会更好吗? 我怀疑这是繁重的工作,为什么只问用户而不是做工作就更容易了。 你怎么认为?我们要去哪里?这是现实的期望吗?还是我有一个错误的

    • 在我设法为我的类类型重载之后,我现在想要专门化它,而不是重载它,因为标准允许向名称空间添加模板专门化。下面是我的例子: 我不知道为什么它没有编译,并且我得到了一个错误:

    • 问题内容: 当您使用Exception类扩展一个类(用于创建新的异常)时,会收到警告,提示您有一个。我知道这在序列化和反序列化过程中起着重要的作用,但是何时需要序列化我的Exception?谁能给我一个实际的案例,让我的自定义异常类具有序列化和反序列化? 问题答案: 这是因为所有异常的根类都实现了接口。默认情况下,所有异常都是可序列化的,这是一种语言设计决策,因为作者希望异常能够在没有任何特殊配置

    • 目前,我正在尝试在这里实现汽车示例的变体: https://www.elastic.co/blog/managing-relations-inside-elasticsearch 如果我运行: 代码工作正常。 但是如果我删除索引并将2015从字符串更改为数字: 我收到以下错误消息: {“error”:{“root\u-cause”:[{“type”:“非法\u-argument\u-excepti

    • 我得到了这个错误:这个表达式的类型是'void',所以它的值不能被使用。尝试检查是否使用了正确的API;可能会有一个函数或调用返回您意想不到的void。还要检查类型参数和变量,它们也可能是空的。 代码: null null 我不明白这是什么。我是新手。这是我的第一个应用程序。有人能帮我一下吗。

    • 问题内容: 我不明白为什么这会使编译器感到困惑。我正在使用泛型类型来保存与和方法无关的对象。我一直认为并且在功能上是相同的,但是我一定会误会。当上课的时候我得到了。这堂课符合我的期望。这里发生了什么? 问题答案: 关于原始类型是如何工作的(您已省略了参数的泛型类型)是,它们的 所有 泛型及其方法也将被删除。因此,对于raw ,和方法 也将 丢失其泛型。