Why use non-member begin and end functions in C++11?
每个标准容器都有一个返回该容器迭代器的
1 2 | auto i = v.begin(); auto e = v.end(); |
你会写
1 2 3 4 | using std::begin; using std::end; auto i = begin(v); auto e = end(v); |
在他的谈话中,编写现代C++时,Herb Sutter说,当你想要一个容器的开始或结束迭代器时,你现在应该总是使用自由函数。然而,他并没有详细说明你为什么要这样做。查看代码,它可以保存所有字符。因此,就标准容器而言,自由功能似乎完全无用。Herb Sutter表示,对于非标准容器有好处,但他再次没有详细说明。
所以,问题是,除了调用相应的成员函数版本,
如何在C数组上调用
自由函数允许更多的通用编程,因为它们可以在以后添加到您无法更改的数据结构上。
当您有包含类的库时,请考虑这种情况:
1 | class SpecialArray; |
它有两种方法:
1 2 | int SpecialArray::arraySize(); int SpecialArray::valueAt(int); |
要迭代它的值,需要从这个类继承并为以下情况定义
1 2 | auto i = v.begin(); auto e = v.end(); |
但如果你总是用
1 2 | auto i = begin(v); auto e = end(v); |
您可以这样做:
1 2 3 4 5 6 7 8 9 10 11 | template <> SpecialArrayIterator begin(SpecialArray & arr) { return SpecialArrayIterator(&arr, 0); } template <> SpecialArrayIterator end(SpecialArray & arr) { return SpecialArrayIterator(&arr, arr.arraySize()); } |
其中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 }; |
现在,
使用
在这种情况下,我可以想到一些用途。
最明显的用途是用于C数组(而不是C指针)。
另一种方法是尝试在不一致的容器上使用标准算法(即容器缺少
另一方面,下一个C++ Rev应该复制D的伪成员表示法。如果没有定义
要回答您的问题,默认情况下,自由函数begin()和end()只调用容器的member.begin()和.end()函数。从
1 2 3 4 | template< class C > auto begin( C& c ) -> decltype(c.begin()); template< class C > auto begin( const C& c ) -> decltype(c.begin()); |
问题的第二部分是,如果自由函数只调用成员函数,那么为什么它们更喜欢自由函数呢?这实际上取决于示例代码中的对象
1 2 3 4 5 6 | 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字符串、枚举等的代码。通过使用非成员函数,可以宣传一个更通用的接口,人们可以轻松地扩展它。通过使用自由功能界面:
1 2 3 4 5 6 | 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字符串。现在编写少量适配器代码:
1 2 3 4 | 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); } |
我们也可以让您的代码与ITerable枚举兼容。我认为Herb的主要观点是,使用自由函数和使用成员函数一样简单,它使代码与C序列类型向后兼容,与非STL序列类型(以及未来的STL类型)向前兼容。对其他开发人员来说成本很低。
型
如果您想使用带有基于范围的for循环或模板的
如果类没有提供这些方法,那不是问题。如果没有,你必须修改它*。
这并不总是可行的,例如在使用外部库时,特别是商业和封闭源代码。
在这种情况下,
示例:假设您希望实现接受容器的
1 2 3 4 5 6 7 8 9 | 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)); } |
现在,对于任何你想与这个自定义的
现在,C++有一个叫做参数依赖查找的机制。(ADL),这使得这种方法更加灵活。
简而言之,adl意味着当编译器解析一个非限定函数(即没有名称空间的函数,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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); |
号
在这种情况下,限定名为
这也是
我们可以吃曲奇饼和曲奇饼-也就是说,有一种方法来提供自定义实现在编译程序可以恢复到标准的情况下。
一些注意事项:
- 百万千克1
出于同样的原因,还有其他类似的功能:
百万千克1百万千克1
正如其他答案所提到的,
百万千克1百万千克1
在编写模板代码时,使用
百万千克1
型
另外,我知道这篇文章已经快7岁了。我遇到它是因为我想回答一个标记为重复的问题,发现这里没有答案提到ADL。
而非成员函数并不能为标准容器提供任何好处,使用它们可以加强更一致和更灵活的风格。如果您在某个时候想要扩展一个现有的非标准容器类,您宁愿定义自由函数的重载,而不是修改现有类的定义。因此,对于非标准容器,它们非常有用,并且总是使用自由函数使代码更加灵活,因为您可以更容易地用非标准容器替换标准容器,并且底层容器类型对代码更透明,因为它支持更广泛的容器实现。
当然,这总是需要适当地加权,过度抽象也不好。虽然使用自由函数并不是太抽象,但它打破了与C++ 03代码的兼容性,在C++ 11的这个年轻时代,代码可能仍然是一个问题。