template<typename _InputIterator, typename _Function>
_Function
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_requires_valid_range(__first, __last);
for ( ; __first != __last; ++__first)
__f(*__first);
return __f;
}
由于先执行了_f函数,才对迭代器进行++,这样如果_f中的操作,使迭代器失效,则++后的行为未定义,导致出错。
比如以下代码:
main.cpp
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
list<int> testlist;
void testfun(int elem)
{
cout<<elem<<" ";
if(elem == 5)
testlist.remove(elem);
}
int main(int argc, char *argv[])
{
for(int i=0; i<10; ++i)
testlist.push_back(i);
for_each(testlist.begin(),testlist.end(),testfun);
system("PAUSE");
return 0;
}
运行结果为 0 1 2 3 4 5 之后可能内存访问异常,coredump。
而如果for_each的实现版本改为:
template<typename _InputIterator, typename _Function>
_Function
for_each_my(_InputIterator __first, _InputIterator __last, _Function __f)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_requires_valid_range(__first, __last);
for ( ; __first != __last;)
__f(*__first++);
return __f;
}
先迭代器++,然后执行_f函数,这样即使_f使当前迭代器失效,也不会影响下面元素的操作。
上面的代码,如果用这个for_each实现,就能正确打印 0 1 2 3 4 5 6 7 8 9
请问为什么for_each用第一种实现方法?第二种岂非更安全些?不可能是stl的实现者没想到,那是什么原因?请大侠指点。
20 个解决方案
#1
效率的考虑,++前缀直接运算后返回,后缀需要返回之前的值,很明显,你这里的例子是一种非正常的编程行为
#2
++做为后缀,会先对当前值进行一次备份,然后对原有值++,实际操作的则是备份的数据
对于大量数据的便利操作,foreach或许是出于对效率的考虑吧,个人猜测
对于大量数据的便利操作,foreach或许是出于对效率的考虑吧,个人猜测
#3
对,后++需要返回自加之前的值,效率低一些。但明显更安全一些,在迭代过程中删除修改元素,是使用stl常犯的错误,为什么stl不通过内部实现来降低使用者的风险呢?
是因为,效率和安全性相比,效率更重要一些?
还是stl专门要如此实现,就是防止程序员有类似使用?在迭代过程中同时删除和处理元素,感觉也是比较正常的需求呀?
#4
lz, 你提的问题很好。sgi之所这样实现有前提的。先看sgi中for_each的模板声明:
你应该注意到,for_each要求输入的迭代器是InputIterator类型的迭代器。而InputIterator迭代器的要求是:能读不能写,并且支持自增运算。这也要求你传递给for_each模板的函数或者函数对象__f不能对容器进行修改删除。所以也就不存在调用fun后,迭代器失效的情况。上述,解释你第一个疑问。
此外我再提一点:
以上两种写法完全等价。只是编码风格不一样。我举一个例子:
最后,说明一点:在使用sgi算法的时候一定要关注迭代器的种类。迭代器的种类一共有五种。(InputIterator, OutputIterator, ForwwardIterator, bidirectionalIterator, random-access Iterator)
参考文献:
c++ primer 中文版 第四版, 11.3.5 五种迭代器
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
你应该注意到,for_each要求输入的迭代器是InputIterator类型的迭代器。而InputIterator迭代器的要求是:能读不能写,并且支持自增运算。这也要求你传递给for_each模板的函数或者函数对象__f不能对容器进行修改删除。所以也就不存在调用fun后,迭代器失效的情况。上述,解释你第一个疑问。
此外我再提一点:
for ( ; __first != __last; ++__first)
__f(*__first);
for ( ; __first != __last;)
__f(*__first++);
以上两种写法完全等价。只是编码风格不一样。我举一个例子:
supertool@supertool-desktop:~/test$ cat test.cpp
/*
* File: test.cpp
*/
#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i != 10; ++i)
cout << i << " ";
cout << endl;
for (int i = 0; i != 10; )
cout << i++ << " ";
cout << endl;
return 0;
}
supertool@supertool-desktop:~/test$ c++ -g -Wall test.cpp -o test
supertool@supertool-desktop:~/test$ ./test
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
最后,说明一点:在使用sgi算法的时候一定要关注迭代器的种类。迭代器的种类一共有五种。(InputIterator, OutputIterator, ForwwardIterator, bidirectionalIterator, random-access Iterator)
参考文献:
c++ primer 中文版 第四版, 11.3.5 五种迭代器
#5
这个是防君子不防小人的。
#6
for_each 是用来遍历的,不是用来修改的,它也只不过提醒你用input iterator ,你用不用就是你的事了。
它不做任何检查,只要你敢用.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int arr[]=
{
1,2,3,4,5
};
vector<int> vec(arr,arr+5);
void func(int &n)
{
++n;
}
void print(int n)
{
cout<<n<<" ";
}
int main()
{
for_each(vec.begin(),vec.end(),func);
for_each(vec.begin(),vec.end(),print);
cout<<endl;
return 0;
}
它不做任何检查,只要你敢用.
#7
这点也是我疑惑的问题,既然是声明为InputIterator类型的迭代器,为什么可以修改删除元素?
《C++标准模板库》中描述for_each算法时,描述为"它可以不同的方式存取、处理、修改每一个元素"。
另外你说的两种写法完全等价,我不认同,起码效率上就不一样。
而且行为上也可能造成差异,你看我1楼的例子,测试代码完全一样,就是因为for_each的两种不同实现,就是不同的结果。
#8
声明为InputIterator类型
表示这个算法能接受的最低阶的迭代器类型。没没有其他含义。
for_each是一个不应该引起质变的算法。
表示这个算法能接受的最低阶的迭代器类型。没没有其他含义。
for_each是一个不应该引起质变的算法。
#9
#10
for_each 是一个函数,它没办法限制迭代器的类型,重载不现实,因为迭代器类型千千万万 .
特化不可能,因为只有类可以特化. 所以,它只能这样办,告诉用户请传入一个只读迭代器,告诉用户别修改数
据.
特化不可能,因为只有类可以特化. 所以,它只能这样办,告诉用户请传入一个只读迭代器,告诉用户别修改数
据.
#11
"它可以不同的方式存取、处理、修改每一个元素"。
而不是删除元素,因为那是在操作容器!
而不是删除元素,因为那是在操作容器!
#12
早上起来又思考了一下,其实除了效率之外,for_each根本无法保证对迭代器操作的后果
以lz的例子来说,list<>中remove一个元素是摘除操作,也就是不影响++之后的迭代器的有效性
但是,如果我写一个新的模板,其中对于remove的实现是在摘除之后对剩余元素进行重新排序,那么,使用后缀++也不能避免_f中使用remove后下个元素的失效问题
毕竟,stl讲究的是泛用性和效率,如果希望库或者语言本身来替代程序员做很多安全性方面的检查,C/C++并非一个很好的选择
以lz的例子来说,list<>中remove一个元素是摘除操作,也就是不影响++之后的迭代器的有效性
但是,如果我写一个新的模板,其中对于remove的实现是在摘除之后对剩余元素进行重新排序,那么,使用后缀++也不能避免_f中使用remove后下个元素的失效问题
毕竟,stl讲究的是泛用性和效率,如果希望库或者语言本身来替代程序员做很多安全性方面的检查,C/C++并非一个很好的选择
#13
f must not modify the order of the sequence.
人家在算法前面就提醒过使用者了,不应该修改元素顺序。所以这个属于防君子不防小人的。
人家在算法前面就提醒过使用者了,不应该修改元素顺序。所以这个属于防君子不防小人的。
#14
C++标准程序库 作者说了,STL更注重的是效率,不是安全。。。
#15
谢谢提醒,才搞明白申明inputIterator是表示算法能接受的最低阶的迭代器类型,这也说明for_each可作用于所有标准容器上,不表示算法对元素只读。
#16
谢谢大侠,原来我搞混了这两个概念,对,操作元素和操作容器是有区别的。
《C++标准模板库》把for_each归为变动性算法,是指存取、处理、修改元素,而不是容器。
#17
正常来说任何对容器造成重新排序的操作都是被严格禁止的
#18
这个后来我也想到了,如果对op函数对容器的改变,造成了所有迭代器都无效,那么先++迭代器也没用了。
看来,stl的确是要求程序员自己保证迭代器的有效性了!
#19
谢谢回复的各位,多谢了。
#20
lz,针对你的疑问,我重新审核前面的解释,发现之前的解释存在问题。看了你这个回复,跟同事探讨后。发现我自己也混淆了两个概念。for_each中要求输入的迭代器是input iteraor, 应该理解成for_each要求的迭代器类型最低标准是InputerIterator, 因为这个算法要求进行++操作和*(引用,即读)操作。第二:我混淆了针对容器操和元素的操作的概念。准确的说是标准库里面的算法不执行针对容器的操作,但是允许修改容器里面的元素。当然如果你要修改你的元素,那么你传递给for_each的迭代器同时必须是OutputIterator类型。
总结:for_each支持针对容器元素的修改,前提是你传递给它的迭代器也支持写操作;但是直接使用for_each对容器进行删除操作是不允许的。
此外,lz那两种写法,在迭代器合法的前提下,是等价的,而且也不存在性能上的差别。如果在迭代器出现非法的情况,那两种写法存在差别。先解释那种写法不存在性能的差别,再解释如果迭代器非法时,那两种的写法的差别。
第一种写法:
for ( ; __first != __last; ++__first)
__f(*__first);
第二种写法:
for ( ; __first != __last;)
__f(*__first++);
上面第一种写法的流程如下:
先判断__first != __last
接下来对__first进行解引用 __f(*__first);
然后对__first 直接自加1: ++__first
之后再进行__first != __last的判断。。。。
(这里罗嗦两句,++__first, 在底层实现的时候是直接递增1,并不对__first的值进行保存;
而__first++, 在底层实现的时候首先把__first值先保存起来,然后才对__first的值进行自加1操作。
这两个的区别在于如果你不需要使用到__first的值的时候,使用++__first会比__first++会省掉保存那个步骤的开销。这也是大家经常建议使用++i代替i++的理由,大这些都是有前提的。)
上面第二种写法的流程如下:
先判断__first != __last
接下来对__first++操作,正如刚才的解释,__first++再对自己进行自加1操作时候,会把自己的值先报存起来;再对__first++操作后,进行解引用操作,这时候引用的对象是__first自加1时所保存的值所指向的对象: __f(*__first++);
然后再进行__first != __last的判断。。。。
综上:可以得出这样的一个结论:当迭代器始终是合法的时候,这两种写法是等价的,性能上也是等价的。
下面解释当迭代器如果遇到非法的时候两者不同的反应:
在你所举的例子中:当元素5被删除的时候,其对应的迭代器也失效:
对于第一种写法:
当*__first = 5时,执行__f(*__first)后,__first已经失效,所以之后针对__first进行++__first操作就属于非法的操作了。
对于第二种写法:
当*__first = 5时,再执行函数__f(*__first++)之前,根据刚才的解释__first已经指向元素6,当函数后,指向5的迭代器失效,但是这时候__first已经指向元素6了。所以程序可以被继续执行下去。
在你的例子,程序之所以能够执行下去还有一个重要的原因。那就是因为,在删除list元素的时候,只有指向这个元素本身的迭代器失效,这个是由list(链表)的特性决定的。如果换一种数据类型或者容器,如果针对某个元素进行删除会导致整个容器所有迭代器都失效,第二种写法,也可能会core掉。
综上: 如果某种操作会导致迭代器部分失效,则上述两种写法可能存在差别。
ps:
本人更倾向第一种写法,因为这种写法能够把这种可能出现的误用尽快的暴露出来。
#21
#1
效率的考虑,++前缀直接运算后返回,后缀需要返回之前的值,很明显,你这里的例子是一种非正常的编程行为
#2
++做为后缀,会先对当前值进行一次备份,然后对原有值++,实际操作的则是备份的数据
对于大量数据的便利操作,foreach或许是出于对效率的考虑吧,个人猜测
对于大量数据的便利操作,foreach或许是出于对效率的考虑吧,个人猜测
#3
对,后++需要返回自加之前的值,效率低一些。但明显更安全一些,在迭代过程中删除修改元素,是使用stl常犯的错误,为什么stl不通过内部实现来降低使用者的风险呢?
是因为,效率和安全性相比,效率更重要一些?
还是stl专门要如此实现,就是防止程序员有类似使用?在迭代过程中同时删除和处理元素,感觉也是比较正常的需求呀?
#4
lz, 你提的问题很好。sgi之所这样实现有前提的。先看sgi中for_each的模板声明:
你应该注意到,for_each要求输入的迭代器是InputIterator类型的迭代器。而InputIterator迭代器的要求是:能读不能写,并且支持自增运算。这也要求你传递给for_each模板的函数或者函数对象__f不能对容器进行修改删除。所以也就不存在调用fun后,迭代器失效的情况。上述,解释你第一个疑问。
此外我再提一点:
以上两种写法完全等价。只是编码风格不一样。我举一个例子:
最后,说明一点:在使用sgi算法的时候一定要关注迭代器的种类。迭代器的种类一共有五种。(InputIterator, OutputIterator, ForwwardIterator, bidirectionalIterator, random-access Iterator)
参考文献:
c++ primer 中文版 第四版, 11.3.5 五种迭代器
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
你应该注意到,for_each要求输入的迭代器是InputIterator类型的迭代器。而InputIterator迭代器的要求是:能读不能写,并且支持自增运算。这也要求你传递给for_each模板的函数或者函数对象__f不能对容器进行修改删除。所以也就不存在调用fun后,迭代器失效的情况。上述,解释你第一个疑问。
此外我再提一点:
for ( ; __first != __last; ++__first)
__f(*__first);
for ( ; __first != __last;)
__f(*__first++);
以上两种写法完全等价。只是编码风格不一样。我举一个例子:
supertool@supertool-desktop:~/test$ cat test.cpp
/*
* File: test.cpp
*/
#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i != 10; ++i)
cout << i << " ";
cout << endl;
for (int i = 0; i != 10; )
cout << i++ << " ";
cout << endl;
return 0;
}
supertool@supertool-desktop:~/test$ c++ -g -Wall test.cpp -o test
supertool@supertool-desktop:~/test$ ./test
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
最后,说明一点:在使用sgi算法的时候一定要关注迭代器的种类。迭代器的种类一共有五种。(InputIterator, OutputIterator, ForwwardIterator, bidirectionalIterator, random-access Iterator)
参考文献:
c++ primer 中文版 第四版, 11.3.5 五种迭代器
#5
这个是防君子不防小人的。
#6
for_each 是用来遍历的,不是用来修改的,它也只不过提醒你用input iterator ,你用不用就是你的事了。
它不做任何检查,只要你敢用.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int arr[]=
{
1,2,3,4,5
};
vector<int> vec(arr,arr+5);
void func(int &n)
{
++n;
}
void print(int n)
{
cout<<n<<" ";
}
int main()
{
for_each(vec.begin(),vec.end(),func);
for_each(vec.begin(),vec.end(),print);
cout<<endl;
return 0;
}
它不做任何检查,只要你敢用.
#7
这点也是我疑惑的问题,既然是声明为InputIterator类型的迭代器,为什么可以修改删除元素?
《C++标准模板库》中描述for_each算法时,描述为"它可以不同的方式存取、处理、修改每一个元素"。
另外你说的两种写法完全等价,我不认同,起码效率上就不一样。
而且行为上也可能造成差异,你看我1楼的例子,测试代码完全一样,就是因为for_each的两种不同实现,就是不同的结果。
#8
声明为InputIterator类型
表示这个算法能接受的最低阶的迭代器类型。没没有其他含义。
for_each是一个不应该引起质变的算法。
表示这个算法能接受的最低阶的迭代器类型。没没有其他含义。
for_each是一个不应该引起质变的算法。
#9
#10
for_each 是一个函数,它没办法限制迭代器的类型,重载不现实,因为迭代器类型千千万万 .
特化不可能,因为只有类可以特化. 所以,它只能这样办,告诉用户请传入一个只读迭代器,告诉用户别修改数
据.
特化不可能,因为只有类可以特化. 所以,它只能这样办,告诉用户请传入一个只读迭代器,告诉用户别修改数
据.
#11
"它可以不同的方式存取、处理、修改每一个元素"。
而不是删除元素,因为那是在操作容器!
而不是删除元素,因为那是在操作容器!
#12
早上起来又思考了一下,其实除了效率之外,for_each根本无法保证对迭代器操作的后果
以lz的例子来说,list<>中remove一个元素是摘除操作,也就是不影响++之后的迭代器的有效性
但是,如果我写一个新的模板,其中对于remove的实现是在摘除之后对剩余元素进行重新排序,那么,使用后缀++也不能避免_f中使用remove后下个元素的失效问题
毕竟,stl讲究的是泛用性和效率,如果希望库或者语言本身来替代程序员做很多安全性方面的检查,C/C++并非一个很好的选择
以lz的例子来说,list<>中remove一个元素是摘除操作,也就是不影响++之后的迭代器的有效性
但是,如果我写一个新的模板,其中对于remove的实现是在摘除之后对剩余元素进行重新排序,那么,使用后缀++也不能避免_f中使用remove后下个元素的失效问题
毕竟,stl讲究的是泛用性和效率,如果希望库或者语言本身来替代程序员做很多安全性方面的检查,C/C++并非一个很好的选择
#13
f must not modify the order of the sequence.
人家在算法前面就提醒过使用者了,不应该修改元素顺序。所以这个属于防君子不防小人的。
人家在算法前面就提醒过使用者了,不应该修改元素顺序。所以这个属于防君子不防小人的。
#14
C++标准程序库 作者说了,STL更注重的是效率,不是安全。。。
#15
谢谢提醒,才搞明白申明inputIterator是表示算法能接受的最低阶的迭代器类型,这也说明for_each可作用于所有标准容器上,不表示算法对元素只读。
#16
谢谢大侠,原来我搞混了这两个概念,对,操作元素和操作容器是有区别的。
《C++标准模板库》把for_each归为变动性算法,是指存取、处理、修改元素,而不是容器。
#17
正常来说任何对容器造成重新排序的操作都是被严格禁止的
#18
这个后来我也想到了,如果对op函数对容器的改变,造成了所有迭代器都无效,那么先++迭代器也没用了。
看来,stl的确是要求程序员自己保证迭代器的有效性了!
#19
谢谢回复的各位,多谢了。
#20
lz,针对你的疑问,我重新审核前面的解释,发现之前的解释存在问题。看了你这个回复,跟同事探讨后。发现我自己也混淆了两个概念。for_each中要求输入的迭代器是input iteraor, 应该理解成for_each要求的迭代器类型最低标准是InputerIterator, 因为这个算法要求进行++操作和*(引用,即读)操作。第二:我混淆了针对容器操和元素的操作的概念。准确的说是标准库里面的算法不执行针对容器的操作,但是允许修改容器里面的元素。当然如果你要修改你的元素,那么你传递给for_each的迭代器同时必须是OutputIterator类型。
总结:for_each支持针对容器元素的修改,前提是你传递给它的迭代器也支持写操作;但是直接使用for_each对容器进行删除操作是不允许的。
此外,lz那两种写法,在迭代器合法的前提下,是等价的,而且也不存在性能上的差别。如果在迭代器出现非法的情况,那两种写法存在差别。先解释那种写法不存在性能的差别,再解释如果迭代器非法时,那两种的写法的差别。
第一种写法:
for ( ; __first != __last; ++__first)
__f(*__first);
第二种写法:
for ( ; __first != __last;)
__f(*__first++);
上面第一种写法的流程如下:
先判断__first != __last
接下来对__first进行解引用 __f(*__first);
然后对__first 直接自加1: ++__first
之后再进行__first != __last的判断。。。。
(这里罗嗦两句,++__first, 在底层实现的时候是直接递增1,并不对__first的值进行保存;
而__first++, 在底层实现的时候首先把__first值先保存起来,然后才对__first的值进行自加1操作。
这两个的区别在于如果你不需要使用到__first的值的时候,使用++__first会比__first++会省掉保存那个步骤的开销。这也是大家经常建议使用++i代替i++的理由,大这些都是有前提的。)
上面第二种写法的流程如下:
先判断__first != __last
接下来对__first++操作,正如刚才的解释,__first++再对自己进行自加1操作时候,会把自己的值先报存起来;再对__first++操作后,进行解引用操作,这时候引用的对象是__first自加1时所保存的值所指向的对象: __f(*__first++);
然后再进行__first != __last的判断。。。。
综上:可以得出这样的一个结论:当迭代器始终是合法的时候,这两种写法是等价的,性能上也是等价的。
下面解释当迭代器如果遇到非法的时候两者不同的反应:
在你所举的例子中:当元素5被删除的时候,其对应的迭代器也失效:
对于第一种写法:
当*__first = 5时,执行__f(*__first)后,__first已经失效,所以之后针对__first进行++__first操作就属于非法的操作了。
对于第二种写法:
当*__first = 5时,再执行函数__f(*__first++)之前,根据刚才的解释__first已经指向元素6,当函数后,指向5的迭代器失效,但是这时候__first已经指向元素6了。所以程序可以被继续执行下去。
在你的例子,程序之所以能够执行下去还有一个重要的原因。那就是因为,在删除list元素的时候,只有指向这个元素本身的迭代器失效,这个是由list(链表)的特性决定的。如果换一种数据类型或者容器,如果针对某个元素进行删除会导致整个容器所有迭代器都失效,第二种写法,也可能会core掉。
综上: 如果某种操作会导致迭代器部分失效,则上述两种写法可能存在差别。
ps:
本人更倾向第一种写法,因为这种写法能够把这种可能出现的误用尽快的暴露出来。