在映射上迭代时使用++it或it++

++it or it++ when iterating over a map?

本文关键字:++it it++ 映射 迭代      更新时间:2023-10-16

显示如何迭代std::map的示例通常是这样的:

MapType::const_iterator end = data.end(); 
for (MapType::const_iterator it = data.begin(); it != end; ++it)

。用++it代替it++。有什么原因吗?如果我用it++代替会有问题吗?

it++返回前一个迭代器的副本。由于没有使用这个迭代器,这是浪费的。++it返回对自增迭代器的引用,避免了复制操作。

为了进行测试,我制作了三个源文件:

#include <map>
struct Foo { int a; double b; char c; };
typedef std::map<int, Foo> FMap;
### File 1 only ###
void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(), end = m.end(); it != end; ++it)
    it->second = f;
}
### File 2 only ###
void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); ++it)
    it->second = f;
}
### File 3 only ###
void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); it++)
    it->second = f;
}
### end ###

g++ -S -O3, GCC 4.6.1编译后,我发现版本2和3产生相同的汇编,版本1只有一条指令不同,cmpl %eax, %esi vs cmpl %esi, %eax

所以,选择适合你的风格。前缀增量++it可能是最好的,因为它最准确地表达了您的需求,但不要为此而烦恼。

使用自增前操作符比使用自增后操作符在性能上有一点优势。在设置使用迭代器的循环时,应该选择使用预增量:

for (list<string>::const_iterator it = tokens.begin();
    it != tokens.end();
    ++it) { // Don't use it++
    ...
}
当您考虑这两个操作符通常如何实现时,原因就一目了然了。预增量非常简单。然而,为了使后增量工作,您需要首先复制对象,在原始对象上执行实际的增量,然后返回副本:
class MyInteger {
private:
    int m_nValue;
public:
    MyInteger(int i) {
        m_nValue = i;
    }
    // Pre-increment
    const MyInteger &operator++() {
        ++m_nValue;
        return *this;
    }
    // Post-increment
    MyInteger operator++(int) {
        MyInteger clone = *this; // Copy operation 1
        ++m_nValue;
        return clone; // Copy operation 2
    }
}

可以看到,增量后实现涉及两个额外的复制操作。如果所讨论的对象体积庞大,这可能会相当昂贵。话虽如此,一些编译器可能足够聪明,可以通过优化处理单个复制操作。关键是后增量通常会比前增量涉及更多的工作,因此明智的做法是习惯将"++"放在迭代器之前,而不是之后。

(1)链接网站的信用。

从逻辑的角度来看,这是一样的,这里没有关系。

为什么使用前缀式——因为它更快——它改变迭代器并返回它的值,而后缀式创建temp对象,对当前迭代器自增,然后返回temp对象(自增之前同一迭代器的副本)。因为这里没有人观察这个临时对象(返回值),所以它是相同的(逻辑上)。

编译器很有可能会优化这个


另外——实际上,对于任何类型都应该是这样的。但这是应该的。因为任何人都可以重载operator++——后缀和前缀,它们可能有副作用和不同的行为。

这是一件很可怕的事情,但仍然是可能的。

这不会引起任何问题,但使用++it更正确。对于小类型,使用++ii++并不重要,但对于"大"类:

operator++(type x,int){
    type tmp=x; //need copy
    ++x;
    return tmp;
}

编译器可能会优化掉其中的一些,但很难确定。

正如其他答案所说,除非它在上下文中不起作用,否则更喜欢++它。对于小类型容器的迭代,它实际上没有什么区别(或者如果编译器将其优化掉,则没有区别),但对于大类型容器,由于节省了创建副本的成本,它可以产生区别。

True,在特定的上下文中,您可能知道该类型足够小,因此您不必担心它。但是稍后,您团队中的其他人可能会将容器的内容更改到重要的位置。另外,我认为最好让自己养成一个好习惯,只有当你知道必须这样做时才进行增量操作。