STL等容器遍历时,采用迭代器的前置自增(自减)操作替代后置自增(自减)操作
【说明】 STL 等容器遍历时,推荐采用迭代器的前置自增(自减)操作替代后置自增(自减)操作,因为前置操作相比后置操作,不需要生成局部临时变量,性能更高。
【原理】 对一个变量的自增或自减操作,均存在两种形式,前置(前缀)操作和后置(后缀)操作,即 ++/-- 操作符可以放在变量左边或右边。自减操作与自增类似,下面以自增为例。
在 C 语言中遍历某个元素序列时,循环变量(假定命名为 i)的自增操作,无论是后置 i++ 还是前置 ++i,编译器均会将其优化为相同的指令。
在 C++ 语言中遍历 STL 容器,或部门内自定义的类 STL 容器时,一般的写法是遍历容器的迭代器,对于迭代器(假定命名为 it)的后置 it++ 和前置 ++it,其操作性能存在一定的差异。
《More Effective C++》的条款6:“自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别”中,详细介绍了两种形式的差别:
// 前置形式:增加然后取回值
UPInt& UPInt::operator++()
{
*this += 1; // 增加
return *this; // 取回值
}
// 后置形式:取回然后增加
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; // 取回值
++(*this); // 增加
return oldValue; // 返回被取回的值
}
后置函数必须建立一个局部临时对象以做为返回值,上述实现代码创建了一个显式的临时对象(oldValue),这个临时对象必须被构造并在最后被析构。前置函数则没有这样的临时对象,其性能更高。
STL 的 stl_iterator.h 实现中,迭代器的前置与后置操作的实现也是类似的:
__normal_iterator& operator++() _GLIBCXX_NOEXCEPT
{
++_M_current;
return *this;
}
__normal_iterator operator++(int) _GLIBCXX_NOEXCEPT
{
return __normal_iterator(_M_current++);
}
因此,除非在需要使用后置操作的场景外,推荐使用前置操作来处理迭代器的自增(自减)。
【注意事项】 不涉及
【例外】 对于关联容器,如 set、map,如果在遍历的过程中删除某些元素,则需要使用后置操作,以 map 示例:
for (auto itMap = someMap.beign(); itMap != someMap.end();) { // 不能在 for 语句中改变迭代器
if (itMap->first == key) {
someMap.erase(itMap++); // erase 之后指向被删除元素的迭代器会失效,map 的 erase 没有返回值,这里只能使用后置形式,让 itMap 指向下一个元素
} else {
++itMap; // 在 else 分支改变迭代器,使用前置形式
}
}
父主题: C++语言