翻译《有关编程、重构及其他的终极问题?》——29.在迭代器上请使用前置自增操作符(++i),不要使用后置自增操作符(i++)
2017-07-15 10:03
295 查看
翻译《有关编程、重构及其他的终极问题?》——29.在迭代器上请使用前置自增操作符(++i),不要使用后置自增操作符(i++)
标签(空格分隔):翻译 技术 C/C++作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
最后更新:2017年07月15日
29.在迭代器上请使用前置自增操作符(++i),不要使用后置自增操作符(i++)
下面的代码来自Unreal Engine 4项目,其中有低效的代码被PVS-Studio诊断为:V803 Decreased performance. In case ‘itr’ is iterator it’s more effective to use prefix form of increment. Replace iterator++ with ++iterator(译者注:大意是说有性能下降的情况,建议把迭代器itr的后置++改成前置++)。void FSlateNotificationManager::GetWindows(....) const { for( auto Iter(NotificationLists.CreateConstIterator()); Iter; Iter++ ) { TSharedPtr<SNotificationList> NotificationList = *Iter; .... } }
解释
如果你没有读这一章的标题,我觉得你可能很难发现上面代码中的问题。第一眼看上去,这部分代码正确无比,但其实并不完美。是的,我说的就是后置自增————“Iter++”。相比后置自增,你绝对应该使用一个前置的方式“++Iter”去代替“Iter++”。为什么要这么做?这么做的价值在哪里?我们接下去继续说。
高效的代码
for( auto Iter(NotificationLists.CreateConstIterator()); Iter; ++Iter)
建议
一般来说,我们都知道前置自增和后置自增的区别(译者注:先自增再取值和先取值再自增的区别),我也希望其内在的结构区别(一般是底层的实现原理)对大家来说一样不是秘密(译者注:可惜大部分人不知道内在区别)。如果你曾经实现过这个自增操作符的重载,你一定就会知道其内在区别。如果没有,我就会在这里做一个简短的解释。(所有不清楚其内在区别的人都可以忽略这一段然后直接往后看,其中有操作符重载的代码示例)
前置自增操作符改变对象的状态,并且返回改变后的对象本身。没有临时的对象需求。前置自增操作符的实现一般如下:
MyOwnClass& operator++() { ++meOwnField; return (*this); }
后置自增操作符也改变了对象的状态,但返回的是前一个状态的对象,这就需要创建一个临时对象来实现。后置自增操作符的实现一般如下:
MyOwnClass operator++(int) { MyOWnCLass tmp = *this; ++(*this); return tmp; }
通过这些代码,你可以看到额外的代码用于创建临时对象了,在实际情况中又如何呢?
今天的编译器已经足够聪明到进行自动优化了,所以如果(临时对象)没有被使用就不会真的创建临时对象。这就是在Release版本中很难看到“it++”和“++it”区别的原因。
但在debug模式下调试程序时,情况就完全不一样了,这是性能上的不同会非常明显。
比如,在这篇文章中就有一些例子来评估在debug模式下使用前置和后置自增操作符的运行时间,我们可以看到使用后置自增操作符基本要多4倍的运行时间。
有人会说“那么在release版本中它们就一样吗?”,可以说他们说的对,但也说的不对。因为我们避免不了在单元测试和调试程序时使用debug版本从而花费更多的时间,但其我们并不希望浪费时间去等待。
总之我认为我们要回答这个问题————“我们应该在迭代器上使用前置自增操作符取代后置自增操作符吗?”,是的,我们应该这么做,这样你就会在debug版本中也能得到更好的速度。而且,一旦这个迭代器很重,那么获益就会更明显。
参考(阅读建议)
在迭代器中使用前置自增操作符取代后置自增操作符吗?
前置 v. 后置自增操作符————性能比较
相关文章推荐
- 翻译《有关编程、重构及其他的终极问题?》——11.不要试图把尽量多的操作符放到一行代码里
- 翻译《有关编程、重构及其他的终极问题?》——22.不要使用#pragram warning(default-X)
- 翻译《有关编程、重构及其他的终极问题?》——28.如果你可以使用简单的函数就不要使用宏
- 翻译《有关编程、重构及其他的终极问题?》——17.使用专门的函数清除专有数据
- 翻译《有关编程、重构及其他的终极问题?》——7.不要在循环中调用alloca()函数
- 翻译《有关编程、重构及其他的终极问题?》——15.在你的代码中开始使用enum class吧
- 翻译《有关编程、重构及其他的终极问题?》——25.不要再用this指针和nullptr比较了
- 翻译《有关编程、重构及其他的终极问题?》——9.使用'-0'符号作为结尾标记
- 翻译《有关编程、重构及其他的终极问题?》——12.当使用拷贝黏贴,一定要特别注意最后一行
- 翻译《有关编程、重构及其他的终极问题?》——5.使用工具去分析你的代码
- 翻译《有关编程、重构及其他的终极问题?》——4.小心--操作符,请把表达式放在括号中
- 翻译《有关编程、重构及其他的终极问题?》——10.避免使用多个小的#ifdef块
- 翻译《有关编程、重构及其他的终极问题?》——27.狡猾的BSTR字符串
- 翻译《有关编程、重构及其他的终极问题?》——24.override和final关键字应该成为你的新朋友
- 翻译《有关编程、重构及其他的终极问题?》——21.正确的检查文件的结尾符(EOF)
- 翻译《有关编程、重构及其他的终极问题?》——3.复制一次,检查两次
- 翻译《有关编程、重构及其他的终极问题?》——18.你在一个语言上积累的经验和知识不总是适用于另外一门语言
- 翻译《有关编程、重构及其他的终极问题?》——13.表格化的格式化
- 翻译《有关编程、重构及其他的终极问题?》——前言
- 翻译《有关编程、重构及其他的终极问题?》——19.如何合理的从一个构造函数中调用另外一个构造函数