【问题标题】:Why using a reference as an iterator为什么使用引用作为迭代器
【发布时间】:2019-10-17 10:54:21
【问题描述】:

我正在学习 std::vector 的 emplace() 并偶然发现了这段代码:

// vector::emplace
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector = {10,20,30};

  auto it = myvector.emplace ( myvector.begin()+1, 100 );
  myvector.emplace ( it, 200 );
  myvector.emplace ( myvector.end(), 300 );

  std::cout << "myvector contains:";
  for (auto& x: myvector)
    std::cout << ' ' << x;
  std::cout << '\n';

  return 0;
}

我想知道为什么在 for 循环中他们使用引用 auto& x 而不是简单的副本,我尝试不使用 & 并且它的工作方式相同,这是避免复制的安全措施还是性能技巧?

【问题讨论】:

  • "避免复制或表演技巧?"两者,因为不复制(通常)可以提高性能
  • @FatihBAKIR 取消引用也不是免费的。有时复制更快,寄存器也很快。你必须测量。
  • @xaxxon 我不知道,因为我没有测量它。我唯一知道的是,在猜测性能方面,直觉几乎不比抛硬币(如果那样的话)好。
  • @FrançoisAndrieux 您说“有时复制速度更快” - 这是事实陈述。我问你怎么知道在某些情况下会发生这样的循环。
  • @FrançoisAndrieux,是的,但是这些与向函数传递 的东西有关,并且ABI 通常需要传递一个指针,这会产生间接开销。但是,这里的循环对编译器是可见的,它不必执行。

标签: c++ loops for-loop reference c++14


【解决方案1】:

就像你说的那样简单,它就是一个副本。所以这确实是一个性能技巧,但对于int,它不会更快,甚至可能更慢。但是如果你有一个包含百万元素的std::vector&lt;std::string&gt;,那么它会产生很大的不同。你可以自己试试。

但是,如果要修改迭代容器的内容,则需要它。如果没有引用,您将更改副本,而不是容器内的元素。在这里可以看出区别:

std::vector<int> numbers1 = {1,2,3,4};
std::vector<int> numbers2 = {1,2,3,4};
for(auto& x: numbers1) ++x;
for(auto x: numbers2) ++x;
assert(numbers1!=numbers2); // True

我还建议使用auto&amp;&amp; 而不是auto&amp;,因为它会更好地与临时人员一起使用,请参阅例如this answer.

【讨论】:

  • " 它可能会更慢。"如何?你必须从内存中加载向量的内容来制作副本,但是一旦加载了,你为什么要把它放在别的地方让它更快呢?
  • @xaxxon "你必须从内存中加载向量的内容来制作副本" 是的,没错,但你只做了一次。然后,您可以在寄存器中或至少在堆栈中使用自己的副本。如果您使用引用/指针,则每次访问都必须取消引用该地址,除非可以证明这些指令之间的值没有改变。对于更长的函数,这可能不容易做到,甚至只是 void foo(int&amp;, int&amp;) 在不知道调用代码的情况下就不可能。
  • 每个访问如果知道它在哪里就不必取消引用它。然后它只是直接指向寄存器。没有实际的取消引用发生。如果它改变了,它的新值仍然在同一个寄存器中。把它放在一个临时变量中不会改变任何东西,除非它可能需要做更多的负载,因为如果你的副本无法优化,你已经增加了工作集的大小
  • @xaxxon 但是调用代码不知道它在哪里。如果你将foo 编译成foo.obj,那么foo 不知道它是如何被调用的。它只知道调用约定,我对它们了解不够,但我从未听说过“此指针指向寄存器”参数类型。这也与 for 循环有关,它通常无法知道向量来自哪里或谁可以访问它。在该循环中调用的任何函数都可能具有涉及更改该向量的副作用。
  • “我建议使用 auto&& 而不是 auto&,因为它会更好地与临时人员一起使用” - 尝试修改容器时不要。你不想在那种情况下需要临时变量,如果你意外得到它们,最好得到一个漂亮的编译器错误。
【解决方案2】:

autoauto&amp; 在此上下文中的另一个区别是auto&amp; 将允许您修改向量中的值。这可能是一个等待发生的不良错误。理想情况下,如果您只是为了阅读而参考,您应该使用 const 参考:const auto &amp;

当向量包含的对象不仅仅是基本的数字或指针类型时,使用引用的好处是它不会将整个对象复制到临时对象。如果对象具有任何深拷贝语义,或者可能是 shared_ptr,那么可能会完全避免显着的开销。

对于基本类型,复制通常非常快,因此首选单个副本,但是如果要求编译器优化器引用基本类型然后多次使用该引用,您可以期望编译器优化器会做“正确的事情”,所以对于模板编程,您应该优先使用 const-ref 而不是副本,以在您不知道类型时保持代码简单。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 2013-07-27
    • 1970-01-01
    • 1970-01-01
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多