每个标准容器都有一个begin和end方法,用于返回该容器的迭代器。然而,c++ 11显然引入了名为std::begin和std::end的自由函数,它们调用begin和end成员函数。所以,与其写

auto i = v.begin();
auto e = v.end();

你会写

auto i = std::begin(v);
auto e = std::end(v);

在他的演讲《Writing Modern c++》中,Herb Sutter说,当你想要容器的开始或结束迭代器时,你应该总是使用free函数。然而,他并没有详细说明为什么你想这样做。看一下代码,它只节省了一个字符。因此,就标准容器而言,免费函数似乎完全无用。Herb Sutter指出,非标准容器也有好处,但他没有详细说明。

因此,问题是std::begin和std::end的自由函数版本除了调用它们对应的成员函数版本之外,还做了什么,以及为什么要使用它们?


当前回答

最终,这样做的好处在于代码是泛化的,因此它与容器无关。它可以操作std::vector、数组或范围,而无需更改代码本身。

此外,容器,甚至是非所有的容器都可以进行改造,以便代码使用基于非成员范围的访问器可以不可知地使用它们。

请看这里了解更多细节。

其他回答

std::begin和std::end的一个好处是它们可以作为扩展点 用于实现外部类的标准接口。

如果你想使用CustomContainer类与基于范围的for循环或模板 函数需要.begin()和.end()方法,显然您必须这样做 实现这些方法。

如果类确实提供了这些方法,那就不是问题。如果没有, 你必须修改它*。

这并不总是可行的,特别是在使用外部库时 商业和封闭源的一个。

在这种情况下,std::begin和std::end可以派上用场,因为可以提供 迭代器API,而不修改类本身,而是重载自由函数。

示例:假设你想实现一个count_if函数,它接受一个容器 而不是一对迭代器。这样的代码可能是这样的:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

现在,对于任何您想与这个自定义count_if一起使用的类,您只有 添加两个自由函数,而不是修改这些类。

现在,c++有一种叫做参数依赖查找的机制 (ADL),这使得这种方法更加灵活。

简而言之,ADL的意思是,当编译器解析一个不合格的函数(即。 函数没有命名空间,比如用begin代替std::begin),它也会 考虑在参数的名称空间中声明的函数。例如:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

在本例中,限定名是some_lib::begin和some_lib::end并不重要 -因为CustomContainer在some_lib:: too中,编译器将在count_if中使用这些重载。

这也是使用std::begin的原因;并且使用std::end;在count_if。 这允许我们使用非限定的begin和end,因此允许ADL和 允许编译器选择std::begin和std::end,当没有其他选择时。

我们可以吃饼干和有饼干-即有一种方法提供自定义实现 的开始/结束,而编译器可以返回到标准的。

一些注意事项:

出于同样的原因,还有其他类似的函数:std::rbegin/rend, Std::size和Std::data。 正如其他答案提到的,std::版本对裸数组有重载。这是有用的, 但这只是我上面所描述的一个特例。 在编写模板代码时,使用std::begin和friends是一个特别好的主意, 因为这使得这些模板更加通用。对于非模板,你可能只是 如果适用,也要使用方法。

附注:我知道这篇文章已经发布将近7年了。我偶然发现它是因为我想 回答一个被标记为重复的问题,发现这里没有答案提到ADL。

为了回答你的问题,空闲函数begin()和end()默认情况下只调用容器的成员函数.begin()和.end()。当你使用任何标准容器(如<vector>, <list>等)时,会自动包含<iterator>,你会得到:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

你的问题的第二部分是为什么更喜欢自由函数,如果他们所做的只是调用成员函数。这取决于示例代码中对象v的类型。如果v的类型是标准容器类型,如vector<T> v;不管你是使用free函数还是成员函数,它们都做同样的事情。如果你的对象v是更通用的,就像下面的代码:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

然后,使用成员函数会破坏T = C数组、C字符串、枚举等的代码。通过使用非成员函数,您可以宣传一个更通用的接口,人们可以轻松地进行扩展。通过使用free函数接口:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

代码现在可以使用T = C数组和C字符串。现在写少量的适配器代码:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

我们也可以让你的代码与可迭代枚举兼容。我认为Herb的主要观点是,使用免费函数就像使用成员函数一样简单,它使您的代码与C序列类型向后兼容,与非stl序列类型(以及未来stl类型!)向前兼容,对其他开发人员来说成本很低。

最终,这样做的好处在于代码是泛化的,因此它与容器无关。它可以操作std::vector、数组或范围,而无需更改代码本身。

此外,容器,甚至是非所有的容器都可以进行改造,以便代码使用基于非成员范围的访问器可以不可知地使用它们。

请看这里了解更多细节。

Whereas the non-member functions don't provide any benefit for the standard containers, using them enforces a more consistent and flexible style. If you at some time want to extend an existing non-std container class, you'd rather define overloads of the free functions, instead of altering the existing class's definition. So for non-std containers they are very useful and always using the free functions makes your code more flexible in that you can substitute the std container by a non-std container more easily and the underlying container type is more transparent to your code as it supports a much wider variety of container implementations.

但当然,这总是需要适当地权衡,过度抽象也不好。尽管使用自由函数并没有那么多的过度抽象,但它仍然破坏了与c++ 03代码的兼容性,在c++ 11的年轻阶段,这对您来说可能仍然是一个问题。

使用begin和end free函数增加了一层间接性。通常这样做是为了有更大的灵活性。

在这种情况下,我可以想到一些用途。

最明显的用途是c数组(而不是c指针)。

另一种情况是试图在不符合标准的容器上使用标准算法(即容器缺少.begin()方法)。假设您不能只修复容器,那么下一个最佳选择就是重载begin函数。Herb建议总是使用begin函数来提高代码的一致性和一致性。而不必记住哪些容器支持方法开始,哪些容器需要函数开始。

顺便说一句,下一个c++版本应该复制D的伪成员表示法。如果a.foo(b,c,d)没有定义,则会尝试foo(a,b,c,d)。这只是一点语法糖,可以帮助我们这些喜欢主语顺序而不是动词顺序的可怜人。