Jon*_*vis 185 c++ iterator stl c++11 container-data-type
每个标准容器具有begin
和end
方法,返回的迭代器是容器.然而,C++ 11显然已经引入自由函数调用std::begin
和std::end
该调用begin
和end
成员函数.所以,而不是写作
auto i = v.begin();
auto e = v.end();
Run Code Online (Sandbox Code Playgroud)
你会写的
using std::begin;
using std::end;
auto i = begin(v);
auto e = end(v);
Run Code Online (Sandbox Code Playgroud)
在他的演讲中,编写现代C++,Herb Sutter说当你想要容器的开始或结束迭代器时,你应该总是使用自由函数.但是,他没有详细说明你想要的原因.查看代码,它可以为您节省一个字符.因此,就标准容器而言,自由函数似乎完全没用.Herb Sutter表示非标准容器有好处,但他再次没有详细说明.
因此,问题是除了调用相应的成员函数版本之外,自由函数版本究竟做了什么,std::begin
并且std::end
做了什么,为什么要使用它们呢?
Mat*_* M. 154
你如何称呼.begin()
和.end()
一个C-阵列上?
自由函数允许更通用的编程,因为它们可以在之后添加到您无法改变的数据结构上.
Gre*_*ape 35
考虑具有包含类的库的情况:
class SpecialArray;
Run Code Online (Sandbox Code Playgroud)
它有2种方法:
int SpecialArray::arraySize();
int SpecialArray::valueAt(int);
Run Code Online (Sandbox Code Playgroud)
迭代它的值你需要从这个类继承并定义begin()
和end()
方法的情况
auto i = v.begin();
auto e = v.end();
Run Code Online (Sandbox Code Playgroud)
但如果你总是使用
auto i = begin(v);
auto e = end(v);
Run Code Online (Sandbox Code Playgroud)
你可以这样做:
template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
return SpecialArrayIterator(&arr, 0);
}
template <>
SpecialArrayIterator end(SpecialArray & arr)
{
return SpecialArrayIterator(&arr, arr.arraySize());
}
Run Code Online (Sandbox Code Playgroud)
在哪里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
};
Run Code Online (Sandbox Code Playgroud)
现在i
,e
可以合法地用于迭代和访问SpecialArray的值
def*_*ode 32
使用begin
和end
free函数添加一层间接.通常这样做是为了提供更大的灵活性.
在这种情况下,我可以想到一些用途.
最明显的用途是C数组(不是c指针).
另一种方法是尝试在不符合要求的容器上使用标准算法(即容器缺少.begin()
方法).假设您不能只修复容器,那么下一个最佳选择是重载该begin
功能.Herb建议您始终使用该begin
功能来提高代码的一致性和一致性.而不是必须记住哪些容器支持方法begin
和哪些需要功能begin
.
顺便说一下,下一个C++ rev应该复制D的伪成员符号.如果a.foo(b,c,d)
没有定义它反而尝试foo(a,b,c,d)
.它只是一点点语法糖来帮助我们那些喜欢主语然后动词排序的穷人.
小智 17
要回答你的问题,默认情况下,自由函数begin()和end()除了调用容器的成员.begin()和.end()函数之外别无其他.从<iterator>
当你使用任何标准的容器,如的,自动列入<vector>
,<list>
等等,您可以:
template< class C >
auto begin( C& c ) -> decltype(c.begin());
template< class C >
auto begin( const C& c ) -> decltype(c.begin());
Run Code Online (Sandbox Code Playgroud)
你问的第二部分是为什么更喜欢自由函数,如果他们所做的只是调用成员函数.这实际上取决于v
示例代码中的对象类型.如果v的类型是标准容器类型,vector<T> v;
那么如果使用free或member函数并不重要,它们会做同样的事情.如果您的对象v
更通用,如下面的代码:
template <class T>
void foo(T& v) {
auto i = v.begin();
auto e = v.end();
for(; i != e; i++) { /* .. do something with i .. */ }
}
Run Code Online (Sandbox Code Playgroud)
然后使用成员函数来破坏T = C数组,C字符串,枚举等的代码.通过使用非成员函数,您可以宣传人们可以轻松扩展的更通用的界面.通过使用免费功能界面:
template <class T>
void foo(T& v) {
auto i = begin(v);
auto e = end(v);
for(; i != e; i++) { /* .. do something with i .. */ }
}
Run Code Online (Sandbox Code Playgroud)
该代码现在适用于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); }
Run Code Online (Sandbox Code Playgroud)
我们也可以使您的代码与可迭代枚举兼容.我认为Herb的主要观点是使用自由函数就像使用成员函数一样简单,它使代码向后兼容C序列类型,并向前兼容非stl序列类型(以及future-stl类型!),与其他开发人员的低成本.
虽然非成员函数不为标准容器提供任何好处,但使用它们会强制执行更一致和灵活的样式.如果您有时想扩展现有的非std容器类,则宁可定义自由函数的重载,而不是更改现有类的定义.因此,对于非std容器,它们非常有用,并且始终使用自由函数使您的代码更加灵活,因为您可以更轻松地用非std容器替换std容器,并且底层容器类型对代码更透明,因为它支持更广泛的容器实现.
但当然,这总是必须加权,而抽象也不好.虽然使用自由函数并不是一个过度抽象的东西,但它仍然破坏了与C++ 03代码的兼容性,在这个年轻的C++ 11代码可能仍然是一个问题.
的一个好处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));
}
Run Code Online (Sandbox Code Playgroud)
现在,对于您要使用此自定义的任何类count_if
,您只需添加两个自由函数,而不是修改这些类.
现在,C++有一个名为Argument Dependent Lookup (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);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,不要紧,合格的名称是some_lib::begin
和some_lib::end
-因为CustomContainer
是在some_lib::
太,编译器将使用这些重载count_if
.
这也是因为拥有using std::begin;
和using std::end;
进入的原因count_if
.这使我们能够使用不合格begin
和end
,因此允许ADL 和
允许编译器来接std::begin
和std::end
当发现没有其他的替代品.
我们可以吃cookie并拥有cookie - 即有一种方法可以提供begin
/的自定义实现,end
而编译器可以回退到标准的.
一些说明:
出于同样的原因,还有其他类似的功能:std::rbegin
/ rend
,
std::size
和std::data
.
正如其他答案所提到的,std::
版本对裸阵列有重载.这很有用,但只是我上面所描述的特例.
std::begin
在编写模板代码时,使用和朋友特别好,因为这会使这些模板更通用.对于非模板,您也可以在适用时使用方法.
PS我知道这篇文章已有近7年的历史了.我遇到它是因为我想回答一个被标记为重复的问题并发现这里没有回答提到ADL.
归档时间: |
|
查看次数: |
26312 次 |
最近记录: |