std::unordered_set 中的元素如何存储在C++内存中?

How are elements in an std::unordered_set stored in memory in C++?

本文关键字:存储 C++ 内存 unordered set std 元素 何存储      更新时间:2023-10-16

在弄乱类型双关迭代器时,我遇到了这样做的能力

std::vector<int> vec{ 3, 7, 1, 8, 4 };
int* begin_i = (int*)(void*)&*vec.begin();
std::cout << "1st: " << begin_i << " = " << *begin_i << std::endl;
begin_i++;
std::cout << "2nd: " << begin_i << " = " << *begin_i << std::endl;

然后我尝试用std::unordered_set做同样的事情:

std::unordered_set<int> set{ 3, 7, 1, 8, 4 };
for (auto& el : set)
{ // Display the order the set is currently in
std::cout << el << ", ";
}
std::cout << 'n' <<std::endl;
int* begin_i = (int*)(void*)&*set.begin();
std::cout << "1st: " << begin_i << " = " << *begin_i << std::endl;
begin_i++;
std::cout << "2nd: " << begin_i << " = " << *begin_i << std::endl;

但我得到的输出是:

4, 8, 1, 7, 3,
1st: [address] = 4
2nd: [address] = 0

我假设这是因为无序集合的元素位于内存的不同部分?考虑到我还使用基于范围的循环打印了元素的存储顺序,我在这里感到困惑。

我的问题是std::unordered_set如何将其元素存储在内存中?将元素添加到集合中时会发生什么?它在内存中的哪个位置,如果它没有存储在一个类似数组的容器中,元素是一个接一个,它是如何跟踪的?

unordered_set

是使用外部链接实现为哈希表的。

这基本上意味着你有一个链表数组(通常称为"桶"(。因此,要将项目添加到unordered_set首先对要插入的新项目进行哈希处理。然后,您获取该哈希并将其减小到数组当前大小的范围(随着您添加更多项目,该范围可以/将扩展(。然后,将新项目添加到该链表的尾部。

因此,根据哈希产生的值,两个连续插入的项目可能会(并且经常会(插入到表的完全不同的部分的链表中。然后链表中的节点通常会被动态分配,因此即使同一链表中的两个连续项目也可能位于完全不相关的地址。

然而,正如我在之前的回答中指出的那样,标准中实际上比大多数人似乎意识到的要多得多。正如我在那里概述的那样,可能(几乎(有可能违反期望并且仍然(有点(满足标准中的要求,但即使充其量,这样做也是相当困难的。对于大多数实际目的,您可以假设它很像链表的向量。

大多数相同的内容适用于unordered_multiset- 唯一的根本区别是,您可以拥有具有相同键的多个项目,而不是只有一个具有特定键的项目。

同样,还有unordered_mapunordered_multimap,它们再次非常相似,只是它们将存储的东西分离到键和与该键关联的值中,并且当它们进行哈希处理时,只看键部分,而不是值部分(。

与其直接回答这个问题,我想解决"类型双关语"的技巧。(我把它放在引号里,因为提供的代码没有演示类型双关语。也许代码针对这个问题进行了适当的简化。无论如何,*vec.begin()给出了一个int,所以&*vec.begin()是一个int*。进一步投射到void*然后回到int*是净无操作。

代码利用的属性是

*(begin_i       + 1) == *(vec.begin() + 1)  // Using the initial value of begin_i
*(&*vec.begin() + 1) == *(vec.begin() + 1)  // Without using an intermediary

这是连续迭代器的属性,它与连续容器相关联。这些是将其元素存储在相邻内存位置的容器。标准库中的连续容器是stringarrayvector;这些是保证您的技巧起作用的唯一标准容器。在deque上尝试它起初似乎有效,但如果向&*begin()添加足够的内容,尝试将失败。其他容器倾向于单独动态分配元素,因此元素的地址之间不需要有任何关系;元素通过指针而不是位置/索引链接在一起。


这样我就不会忽略所问的问题:

无序集合只是将元素组织到存储桶中。除了要求将所有具有相同哈希值的元素放置在同一个存储桶中外,对如何完成此操作没有任何要求。(这并不意味着同一存储桶中的所有元素都具有相同的哈希值。实际上,每个桶可能被实现为一个list,而桶的容器可能是一个vector,仅仅是因为重用代码很酷。同时,这是一个实现细节,所以它可以非常从一个编译器到另一个编译器,甚至从一个编译器版本到另一个编译器版本。没有保证。

std::unordered_set存储其内存的方式是实现定义的。标准不在乎,只要它满足要求。

在VS版本中,它将它们存储在std::list中(通过创建和管理其他数据提供快速访问( - 因此每个元素也有指向上一个的指针,下一个通过new存储(至少这是我从std::list中记得的(。