我想知道为什么在STL中的许多模板算法中,参数不是通过引用传递的,而是通过值传递的。以下是<iterator
&gt;的示例。头:
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (InputIterator first, InputIterator last);
当我将两个迭代器传递给此函数时,它们将被复制。我天真的想法是,最好通过const-reference传递这些迭代器以避免复制迭代器对象:
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (const InputIterator &first, const InputIterator &last);
可以说迭代器通常是非常小的对象,复制它们并不昂贵。但即便如此:便宜的复制将比没有复制更昂贵。
那么在STL版本中,迭代器是按值传递的原因是什么?
谢谢!
答案 0 :(得分:10)
我想到的一件事是反对引用中的const
:在使用它们时需要修改迭代器。
另一个实现细节可能是迭代器实际上只是作为指针实现的。大多数情况下参考也是如此。如果按值传递指针,则将其复制一次,但仅在需要时取消引用它。但是,如果迭代器指针本身由引用指针传递,则即> strong>必须首先解除引用,只是为了到达迭代器,每次访问迭代器时都必须这样做。这是多余的。
答案 1 :(得分:7)
对于某些廉价复制类型,按值传递它们实际上比通过引用传递更快。来自教程等的传统智慧表明,通过引用更快,但这不一定适用于廉价复制类型。
如何按值传递对象?您复制它,这意味着您获取值并将其推送到堆栈以进行函数调用。你如何通过参考传递?您将内存地址推送到堆栈,然后被调用的函数必须获取该地址的任何内容。现在,优化和缓存可能会发挥作用,使内存提取更便宜,但你仍然没有比从堆栈中获取精确值更便宜。在迭代器的情况下,通常只需要一个简单的指针。这是一个单词长,复制非常便宜。
另外,请注意您建议传递const引用。这意味着它必须在被调用函数中被复制以允许它被修改(例如在循环中递增)。
答案 2 :(得分:5)
std
迭代器的概念是generalisation of a pointer。 std
容器的迭代器通常实现为组成单个指针的类型。如果参数类型的复制价格与指针一样便宜,那么通过引用传递参数比使用值传递它更加更多。必须先取消引用对象,然后才能使用对象的值。有关详细信息,请参阅this answer。
因为几乎所有std
算法都需要复制迭代器,为了获得最佳性能,迭代器的复制成本非常低。出于这个原因,找到一个迭代器是非常不寻常的,它通过值传递比引用要贵得多。
在std::distance
- 以及许多其他算法的情况下 - 整个算法非常简单,编译器很可能会内联调用。如果内联调用,则参数是通过引用还是通过值传递并不重要。 [请注意,内联函数调用与内联函数声明不同!]
如果迭代器通过值传递比通过引用更昂贵,并且函数调用没有内联,则可以通过rvalue-reference创建用于传递迭代器的参数。在如此罕见的情况下,性能提升可能不值得额外的复杂性。
答案 3 :(得分:2)
大多数算法会修改其参数。例如,distance
可以实现如下 1 :
template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (InputIterator first, InputIterator last) {
typename iterator_traits<InputIterator>::difference_type result{};
while (first++ != last)
++result;
return result;
}
如果您将first
作为const
参考传递,显然这不起作用。
如果将它作为非const
引用传递它也不起作用,因为在大多数调用上下文中,调用者的对象无法修改(例如,如果将container.begin()
的结果传递给功能。
当然你仍然可以通过const
引用,在中复制然后修改它。在那一点上,我们什么都没有获得。
结合C ++标准建议,迭代器应该是轻量级类型,复制起来应该很便宜,它更简单,并且在大多数情况下更高效来按值传递它们:
但即便如此:便宜的复制将比没有复制更昂贵。
不正确。您还需要复制引用(在非内联函数调用的情况下,它将作为指针实现)。然后你需要取消引用那个指针也增加了开销。与直接拷贝相比,在许多情况下复制指针可能便宜,并且不会产生任何解除引用的开销。
1 当然,实际的实现将针对随机访问迭代器进行优化,以使其具有常量而非线性运行时。以上实施仅供参考。