如何将std::unordered_set中的元素移动到std::vector?
你提的这个问题确实戳中了std::unordered_set的一个关键设计点——它存储的元素是不可修改的(本质上是const的),这直接导致你没法直接移动它们,因为移动操作的核心是转移资源所有权,而这需要对元素进行非const的操作。
先说说你之前的尝试为什么没用:你用std::make_move_iterator构造vector的写法,看起来是要移动元素,但实际上编译器只能执行拷贝操作。原因很简单:unordered_set的迭代器返回的是const引用,而移动构造函数需要的是非const的右值,编译器找不到匹配的移动构造,就只能退而求其次调用拷贝构造函数了。
那正确的做法是什么呢?其实从C++17开始,标准库给无序容器(包括unordered_set)新增了一个extract方法,这就是解决这个问题的关键。
正确实现移动的代码示例
#include <vector> #include <unordered_set> #include <string> int main() { std::unordered_set<std::string> set; set.insert("a long string number 0000000000000000000000000000001"); set.insert("a long string number 0000000000000000000000000000002"); set.insert("a long string number 0000000000000000000000000000003"); std::vector<std::string> v; v.reserve(set.size()); // 预分配空间,避免vector多次扩容的开销 while (!set.empty()) { // 把第一个元素从unordered_set中"拔"出来,元素不会被销毁,而是被包裹在node_type里 auto node = set.extract(set.begin()); // 将node中的元素移动到vector中,这里是真正的移动操作 v.push_back(std::move(node.value())); } }
为什么这个方法可行?
extract方法的作用是把元素从unordered_set中移除,但不会销毁它——它返回一个node_type对象,这个对象持有被移除的元素,而且你可以通过node.value()获取到非const的元素引用,这就满足了移动构造的要求。当你用std::move(node.value())把元素移动到vector后,原来的node里的元素就处于可析构的空状态,不会有资源泄漏。
你之前提到的直接调用erase方法确实没法移动元素,因为erase会直接销毁元素,没有给你转移所有权的机会。但extract完美解决了这个痛点,相当于给了你一个"转手"元素的机会。
如果你用的是C++17之前的版本怎么办?
很遗憾,C++17之前的标准库没有extract方法,这时候你确实没办法真正移动unordered_set里的元素,只能接受拷贝操作。因为容器的元素是const的,任何尝试修改或移动的操作都会被编译器阻止。这种情况下,你只能老老实实用拷贝构造的方式把元素转到vector里。
另外再补充个小知识点:为什么unordered_set的元素是const的?这是因为它是基于哈希表实现的,元素的哈希值决定了它在容器中的存储位置。如果允许修改元素,可能会导致哈希值变化,破坏整个哈希表的结构,所以标准库直接把元素设为不可修改的,从根源上避免这个问题。
内容来源于stack exchange

