每个标准容器都有一个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的自由函数版本除了调用它们对应的成员函数版本之外,还做了什么,以及为什么要使用它们?
为了回答你的问题,空闲函数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类型!)向前兼容,对其他开发人员来说成本很低。
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的年轻阶段,这对您来说可能仍然是一个问题。
考虑这样一种情况,当你有一个包含类的库:
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的值
使用begin和end free函数增加了一层间接性。通常这样做是为了有更大的灵活性。
在这种情况下,我可以想到一些用途。
最明显的用途是c数组(而不是c指针)。
另一种情况是试图在不符合标准的容器上使用标准算法(即容器缺少.begin()方法)。假设您不能只修复容器,那么下一个最佳选择就是重载begin函数。Herb建议总是使用begin函数来提高代码的一致性和一致性。而不必记住哪些容器支持方法开始,哪些容器需要函数开始。
顺便说一句,下一个c++版本应该复制D的伪成员表示法。如果a.foo(b,c,d)没有定义,则会尝试foo(a,b,c,d)。这只是一点语法糖,可以帮助我们这些喜欢主语顺序而不是动词顺序的可怜人。