何时使用std::begin和std::end而不是特定于容器的版本?

111

有没有一些通用的偏好或规则,可以解释在什么情况下应该使用容器特定版本的begin和end,而不是自由函数std::begin和std::end?

我理解的是,如果该函数是一个模板,其中容器类型是一个模板参数,那么应该使用std::begin和std::end,例如:

template<class T> void do_stuff( const T& t )
{
    std::for_each( std::begin(t), std::end(t), /* some stuff */ );
}

在其他情况下,例如已知容器类型的标准/成员函数等情况下,使用std::begin(cont)std::end(cont)是否仍然是更好的实践,还是应该优先使用容器的成员函数cont.begin()cont.end()
我是否正确地假设通过调用cont.end()而不是std::end(cont)没有性能上的好处?

4
个人而言,我打算总是使用begin(x)而不是x.begin()。这更具适应性(我可以重载begin()来执行正确的操作 - 就像它被用于数组一样)。虽然关键字“auto”解决了不必要地重复写出类型名称的一部分问题,但begin()和end()则完美地解决了这个问题,使得所有内容都可以由编译器从参数本身推导出来。非常简洁。 - Mordachai
10
最常见的用法可能是:使用using std::begin; begin(c);来让ADL发挥作用。 - UncleBens
2
@user7116 我将另一个问题标记为此问题的重复 - 这个问题似乎描述得更好,并且似乎有更详细的答案。或者它们可以合并吗? - BartoszKP
3个回答

85

相较于容器的成员函数,自由函数版本更加通用。我可能会在泛型代码中使用它,因为容器的类型事先不知道(可能是数组)。在其余的代码中(即当容器被固定和已知时),由于惯性,我可能会使用 c.begin()。我期望新的C++教科书推荐使用自由函数版本(因为它从不更差,有时更好),但这需要赶上常见用法。


9
因为惯性所以会发生,+1 是因为提到了这一点。 - xtofl
你不应该没有充分的理由而避免使用 ADL。此外,基于范围的 for 循环是通过在 ADL 中查找 begin 和 end 来定义的。 - Joe
@DavidRodríguez-dribeas 不,这个三步过程适用于常规名称查找,适用于范围基于循环的查找,该查找明确跳过未限定的查找(即使在 DR 发布之前的标准中也直接说明了使用 ADL,请参见 DR1442)。由于在此情况下名称没有被定义限定,因此只剩下 ADL。 - Joe
@DavidRodríguez-dribeas 除了数组的特殊情况并不完全是一个"步骤"之外,我在在线上看到的标准(草案)(n3242)只有两个要点,其中第一个是数组的特殊情况。 - Joe
1
@Joe:你可以在谷歌上搜索,我相信甚至在SO上也有相关问题(https://dev59.com/OnPYa4cB1Zd3GeqPkZK_)。n3337是C++11之后的第一份草案,只进行了标准的编辑更改。维基百科:http://en.wikipedia.org/wiki/C%2B%2B11 - David Rodríguez - dribeas
显示剩余5条评论

44
如果您查看例如std::begin的定义:
template< class C > 
auto begin( C& c ) -> decltype(c.begin());

你会发现,它只是引用了begin()。我想一个好的编译器会把这个差异降至最小,所以最终还是看个人喜好。就我个人而言,我会使用cont.begin()cont.end(),这样我就不必向任何人解释了 :)
如Mooing Duck指出的那样,std::begin也适用于数组:
template< class T, size_t N > 
T* begin( T (&array)[N] );

......所以你需要考虑这一点。如果您没有使用数组,我会选择我的建议。但是,如果您不确定传递的是一个STL容器还是一个<T>数组,则应选择std::begin()


1
我认为,为C结构体专门定制std::begin也可能很方便。不过你的修改已经处理了这个问题,所以加一分。 - Mooing Duck
16
它适用于数组,这就是我总是会使用模板中的std::begin的原因。 - mark
8
[翻译] std::begin() 不仅适用于数组,而且使用它可以让调用者进行特化,例如对于没有常规 begin/end 函数的类型,或者不能添加这些函数的类型。我认为比这更好的方法是 using std::begin; auto beg = begin(c);。在需要特化 std::begin() 的情况下(例如部分特化),这将支持参数相关查找 begin() - boycy
21
我已经点踩了,因为我认为这不是正确的答案。这不是关于个人喜好的问题;std::begin对调用者提供了更多灵活性,不仅适用于数组。 - Jon
我认为您不必拼写using std :: begin,因为参数相关的查找将自动执行,是吗?对我来说是这样。 - iwat0qs

12
除非某些优化被关闭以便进行调试,否则使用cont.begin()(或获取指向第一个元素的指针等)将没有性能好处,除非有人提供了一个非常奇怪的实现!几乎所有的实现(包括STL)都是薄如纸片,在编译器中消失。
好处在于上面的“或其他”:相同的代码可用于不同的集合类型,无论是来自STL、数组还是第三方提供的一些奇怪的集合,只要他们想为它提供begin的特殊化。即使您从未使用过它,begin()是众所周知的,应该会有熟悉度的好处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接