鲲鹏社区首页
中文
注册
开发者
我要评分
获取效率
正确性
完整性
易理解
在线提单
论坛求助

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 分支改变迭代器,使用前置形式
    }
}