从 unordered_map 移动到 unique_ptr 值的最佳方式

Best way to move from unordered_map with unique_ptr values

我有一张这样的地图

std::unordered_map<std::string, std::unique_ptr<V>> map;

我想最终 运行 映射中所有剩余 V 的函数,所以我执行以下操作:

for (auto&& [_, v] : map) {
  func(std::move(v));
}

这行得通,但我一直天真地假设编译器会忽略键值,因为它们没有在 for 循环中使用。现在我认为迭代器产生 const std::string 作为第一个参数,所以确实有很多不必要的字符串构建正在进行。这里到底发生了什么(关于键值)?有没有办法避免任何字符串复制?

在 C++ 标准中,[stmt.ranged] 指定基于范围的 for 循环与某个普通的 for 循环具有等效的语义。在您的情况下,这是:

{
    auto &&__range = map ;
    auto __begin = __range.begin() ;
    auto __end = __range.end() ;
    for ( ; __begin != __end; ++__begin ) {
        auto&& [_, v] = *__begin;
        func(std::move(v));
    }
}

(请注意 : 右侧的表达式或 braced-init-list 成为 auto &&__range 的初始化器,而声明器或 : 左侧的结构化绑定用 *__begin 初始化。这就是一般转换的工作方式。)

在这个完整的表格中,我们可以准确地看到发生了什么。每次取消引用迭代器时,它只会生成对存储在映射中的 (string, unique_ptr) 对的引用。然后结构化绑定声明使 _ 成为引用 string 的左值,而 v 成为引用 unique_ptr 的左值。最后,std::move 将左值转换为右值,并将该右值传递给 func。没有复制字符串。

取消对 std::unordered_map returns 引用的迭代器的引用。然后,具有 auto&& 的结构化绑定指向一个引用,因此不会生成任何副本。

我喜欢使用的一个工具是 C++ Insights。 For this example,我们可以看到所有变量都是引用,并且没有在循环中实例化副本:

int main()
{
  std::unordered_map<std::string, std::unique_ptr<int> > map = ...;
  {
    std::unordered_map<std::basic_string<char, std::char_traits<char>, ...> & __range1 = map;
    std::__detail::_Node_iterator<...> __begin1 = __range1.begin();
    std::__detail::_Node_iterator<...> __end1 = __range1.end();
    for(; std::__detail::operator!=(__begin1, __end1); __begin1.operator++()) 
    {
      std::pair<const std::basic_string<...>, std::unique_ptr<int, ...> & __operator11 = __begin1.operator*();
      const std::basic_string<...>& _ = std::get<0UL>(__operator11);
      std::unique_ptr<int, ...>& v = std::get<1UL>(__operator11);
      func(std::unique_ptr<int, std::default_delete<int> >(std::move(v)));
    }

  }
}