在C++中实现rehash()函数时感到困惑

Confused implementing a rehash() function in C++

本文关键字:函数 C++ 实现 rehash      更新时间:2023-10-16

我的目标是为我在C++模板类中编写的哈希表实现一个rehash()方法。然而,我很困惑我到底可以散列什么,因为这个方法只知道类型是泛型。据我所知,这与Java泛型不同

我对java的理解。。。在Java中,由于每个对象都继承了hashCode()方法,所以我们可以覆盖这个方法。如果用java编写通用哈希表,那么在rehash()过程中,可以简单地调用hashCode()方法来返回值。这可以通过tableSize进行修改。无痛

回到C++。。。由于我不相信我可以简单地在C++泛型上调用hashCode()方法,也不能访问泛型类型的数据成员,所以我不知道我可以在什么上重新散列

我在C++中的泛型类型有可能继承一个抽象类,这样我就可以强制使用一个虚拟hashcode()=0方法吗?或者模板类中的哈希函数依赖于其他类型的非数据成员哈希?

尽量说清楚。感谢任何能为我指明正确方向或提供指导的人。

注意:当然,我认为实现哈希表的目标是为了学习。如果没有,请使用std::unordered_map

这里最重要的一点是您已经注意到的:模板和泛型不是一回事。(好吧,由于类型擦除,java泛型只是一个奇特的语法糖工具,但那是另一回事)。

C++模板为模板的每个不同实例化编写一个类的版本。也就是说,如果在程序中使用std::vector<int>std::vector<bool>,编译器将为类型为int的类向量和类型为bool的类向量生成代码。

模板最强大的方面之一是,每个与类型相关的操作都在编译时进行评估。也就是说,每个模板实例化、别名、typedef等都是在编译时完成的。您在运行时获得的代码是为不同模板实例最终生成的类的组合。因此,您不会像在java或C#这样的OO语言中那样在运行时考虑类型,而是考虑编译时编译结束时必须解决Everithing

现在我们来看一下您的问题:
您希望为您的类型提供一个"hashable"接口",以调用哈希表中的哈希函数。这可以通过两种方式实现:

基于成员功能的方式:

解决问题的一种方法是假设您的类型有一个hash()公共成员函数(就像java中的getHashCode()一样,它是从Object继承的)。因此,在您的哈希表实现中,您使用这个哈希函数,就好像元素拥有它一样。不要担心传递的类型。事实是,如果你这样做,并且你传递了一个没有公共散列成员函数的类型作为模板argument,那么模板就无法实例化。Violá
将模板视为合同:在模板中编写一个完全通用的代码。传递给模板的类型必须完全填充合同。也就是说,必须拥有你认为他们拥有的一切。

这种方法的问题在于,哈希映射中使用的任何类型都必须具有哈希公共成员函数。请注意,通过这种方式,您不能在hashmap中使用基本类型。

基于函数的方式:

函子 是一个充当函数的类。也就是说,该类的实例可以像函数一样使用。例如:

template<typename T>
struct add
{
    T operator()(const T& a , const T& b)
    {
        return a + b;
    }
};

您可以按如下方式使用此函子:

int main()
{
   add<int> adder;
   int a = 1 , b = 2;
   int c = adder(a,b);
}

但函子最重要的用途是基于函子是类的实例,因此可以作为自变量传递到其他站点。也就是说,函子充当高级函数指针。

这用于通用STL算法,如std::find_if:Find if,用于根据搜索标准查找指定间隔的元素。该标准通过一个充当布尔谓词的函子传递。例如:

class my_search_criteria
{
   bool operator()(int element)
   {
      return element == 0;
   }
};  
int main()
{
    std::vector<int> integers = { 5 , 4 , 3 , 2 , 1 , 0 };
    int search_result = std::find_if( std::begin( integers ) , std::end( integers ) , my_search_criteria() );
}

但是,函子如何帮助解决您的问题
您可以实现一个充当哈希函数的通用函子:

template<typename T>
struct hash
{
   unsigned int operator()(const T& element) 
   {
      return /* hash implementation */
   }
};

并在您的哈希表类中使用它:

template<typename T>
class hachtable
{
private:
   hash<T> hash_function;
   std::vector<T> _container;
   void add(const T& element)
   {
      _container.insert(std::begin( _container ) + hash_function( element ) , element);
   }
};

请注意,您需要为元素的类型实现散列。C++模板允许您编写模板的特殊显式事例。例如,您编写了一个泛型数组类,并注意到如果元素的类型是布尔型的,那么将布尔型存储为数字位可能会更高效,以减少内存消耗。使用C++模板,您可以编写特殊情况。您使用显式类型显式编写模板类作为template argument。这被称为"模板专业化"。事实上,"将位用于布尔大小写"的例子正是std::vector所做的。

在我们的例子中,如果我们有一个散列函子的声明:

template<typename T>
struct hash;

我们需要为您将在hashmap中使用的每种类型进行专门化。例如,无符号整数的特殊化:

template<>
struct hash<unsigned int>
{
   unsigned int operator()(unsigned int element)
   {
       return element;
   }
};

基于函子的方法正是C++标准库所做的。它有一个散列函子std::hash的定义,并在散列表实现中使用它,如std::unordered_map。请注意,库中有一组针对基本类型的内置散列专门化。