每个标准容器都有一个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的自由函数版本除了调用它们对应的成员函数版本之外,还做了什么,以及为什么要使用它们?


当前回答

考虑这样一种情况,当你有一个包含类的库:

class SpecialArray;

它有两种方法:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

为了遍历它的值,你需要从这个类继承并定义begin()和end()方法

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

但如果你总是用

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

你可以这样做:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

其中SpecialArrayIterator是这样的:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

现在i和e可以合法地用于迭代和访问SpecialArray的值

其他回答

考虑这样一种情况,当你有一个包含类的库:

class SpecialArray;

它有两种方法:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

为了遍历它的值,你需要从这个类继承并定义begin()和end()方法

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

但如果你总是用

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

你可以这样做:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

其中SpecialArrayIterator是这样的:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

现在i和e可以合法地用于迭代和访问SpecialArray的值

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)。这只是一点语法糖,可以帮助我们这些喜欢主语顺序而不是动词顺序的可怜人。

为了回答你的问题,空闲函数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类型!)向前兼容,对其他开发人员来说成本很低。

如何在c数组上调用.begin()和.end() ?

自由函数允许更多的泛型编程,因为它们可以随后添加到您无法更改的数据结构上。