const_iterator是否更快?

我们的编码指南更喜欢const_iterator ,因为它们比正常的iterator要快一些。 当你使用const_iterator时,编译器看起来好像优化了代码。

这真的是对的吗? 如果是的话,真正发生在内部的是什么让const_iterator更快?

编辑:我写了小testing检查const_iteratoriterator ,发现不同的结果:

对于迭代10,000个对象, const_terator要花费几毫秒(大约16毫秒)。 但并不总是 。 有两个迭代是平等的。

如果没有其他的东西, const_iterator 得更好,因为它告诉任何人阅读代码“我只是迭代这个容器,而不是搞乱包含的对象”。

这是一个伟大的胜利,不用pipe任何性能差异。

我们使用的指导方针是:

总是比非常量更喜欢const

如果你倾向于使用const对象,那么习惯于只对你得到的对象使用常量操作,而尽可能多地使用const_iterator

康斯坦丁具有病毒性质。 一旦你使用它,它会传播到你的所有代码。 你的非变异方法变成常量,这就需要对属性只使用常量操作,并且传递常量引用,本身只强制常量操作。

对我来说,使用常量迭代器而不是非常量迭代器(如果有的话)的性能优势远不如代码本身的改进。 意味着(devise)非突变的操作不变的。

它们是用于非平凡的容器/迭代器。 让自己的习惯保持直线,当重要时不会失去performance。

另外,有几个理由更喜欢const_iterator,无论如何:

  1. 使用const表示代码意图(即只读,不改变这些对象)。
  2. 使用const(_iterator)可以防止意外修改数据。 (往上看)
  3. 一些库使用缺lessconst的begin()将数据标记为脏(即OpenSG),并在同步时将其发送到其他线程/ over-network,因此它具有实际的性能影响。
  4. 而且,允许你访问非const成员函数可能会产生你不打算的副作用(与上面几乎相同),例如从共享数据中分离写时复制容器。 QT为一,就是这样。

作为上面最后一点的一个例子,下面是Qt中qmap.h的摘录:

 inline iterator begin() { detach(); return iterator(e->forward[0]); } inline const_iterator begin() const { return const_iterator(e->forward[0]); } 

即使迭代器和const_iterator实际上是等价的( const除外),如果有两个或多个对象使用它, detach()会创build一个新的数据副本。 如果你只是要读取你使用const_iterator指出的数据,这是完全没有用的。

当然,有另一个方向的数据点:

  1. 对于STL容器和许多简单复制语义容器,性能并不重要。 代码等效的。 但是,能够编写清晰的代码并避免错误胜出。
  2. Const是病毒式的,所以如果你在一个传统的代码库中工作,那么你可能不得不使用非常量迭代器。
  3. 显然,一些pre C ++ 0x STL有一个bug,const_iterators不能用来从容器中擦除()元素。

我不明白他们为什么会这样 – constness是编译时检查。 但明显的答案是写一个testing。

编辑:这是我的testing – 它给我的机器相同的时间:

 #include <vector> #include <iostream> #include <ctime> using namespace std;; int main() { vector <int> v; const int BIG = 10000000; for ( int i = 0; i < BIG; i++ ) { v.push_back( i ); } cout << "begin\n"; int n = 0; time_t now = time(0); for ( int a = 0; a < 10; a++ ) { for( vector <int>::iterator it = v.begin(); it != v.end(); ++it ) { n += *it; } } cout << time(0) - now << "\n"; now = time(0); for ( int a = 0; a < 10; a++ ) { for( vector <int>::const_iterator cit = v.begin(); cit != v.end(); ++cit ) { n += *cit; } } cout << time(0) - now << "\n";; return n != 0; } 

我们的编码准则说优先const_iterator

看看Scott Meyers的这篇文章 。 他解释了为什么比起const_iterator更喜欢迭代器。

这取决于你使用的容器和实现。

是的, const_iterator 可能会更好。

对于一些容器,const迭代器和可变迭代器的实现可能会有所不同 。 我能想到的第一个例子是SGI的STL绳索容器 。 可变的迭代器有附加的指针,以便支持更新。 这意味着额外的资源被浪费在引用计数更新+指向父索引的内存上。 请参阅此处的实现说明 。

但是,正如其他人所说,编译器const使用const来进行优化。 const只是授予对引用对象的只读访问权限,而不是说它是不可变的。 对于像std::vector这样的容器,其迭代器通常被实现为一个简单的指针,不会有任何区别。

它们应该是相同的,因为constness是编译时检查。

为了certificate自己没有怪癖,我拿了匿名代码,修改它使用clock_gettime ,添加了一个外部循环,以避免caching偏见,并跑了很多次。 结果出乎意料地不一致 – 上下20%(没有闲置的盒子) – 但iteratorconst_iterator 最小时间实际上是相同的

然后我得到我的编译器(GCC 4.5.2 -O3)生成汇编输出,并在视觉上比较两个循环: 相同 (除了一对寄存器加载的顺序被颠倒)

iterator循环

  call clock_gettime movl 56(%esp), %esi movl $10, %ecx movl 60(%esp), %edx .p2align 4,,7 .p2align 3 .L35: cmpl %esi, %edx je .L33 movl %esi, %eax .p2align 4,,7 .p2align 3 .L34: addl (%eax), %ebx addl $4, %eax cmpl %eax, %edx jne .L34 .L33: subl $1, %ecx jne .L35 leal 68(%esp), %edx movl %edx, 4(%esp) leal 56(%esp), %esi movl $1, (%esp) 

const_iterator循环:

  movl 60(%esp), %edx movl $10, %ecx movl 56(%esp), %esi .p2align 4,,7 .p2align 3 .L38: cmpl %esi, %edx je .L36 movl %esi, %eax .p2align 4,,7 .p2align 3 .L37: addl (%eax), %ebx addl $4, %eax cmpl %eax, %edx jne .L37 .L36: subl $1, %ecx jne .L38 leal 68(%esp), %edx movl %edx, 4(%esp) leal 56(%esp), %esi movl $1, (%esp) 

当你对这些进行基准testing的时候,一定要使用适当的优化级别 – 使用“-O0”和“-O”等等,你会得到截然不同的计时。

container<T>::const_iterator::operator*返回一个const T&而不是T& ,所以编译器可以对const对象进行通常的优化。

像访问限制(公共,受保护,私有)一样,“规则”对程序员的益处远远超过了对优化的帮助。
(const_cast,可变数据成员,指针/引用别名),编译器实际上无法针对涉及const的情况进行实际优化。 这里最重要的原因是因为const_iterator不允许修改它引用的数据,并不意味着这些数据不能通过其他方式改变。 如果编译器不能确定数据是只读的,那么对于非const迭代器来说,它不能真正进行优化。
更多信息和例子可以在http://www.gotw.ca/gotw/081.htmfind

我的经验,编译器使用常量迭代器时不会做任何可衡量的优化。 尽pipe“可能”这个陈述是正确的,引用并没有被定义为标准中的指针。

然而,你可以有两个引用到同一个对象,所以可以是const,一个非const。 然后,我猜这个线程中的限制指针的答案适用:编译器不能知道对象是否由另一个线程,例如,或由一些中断处理代码更改。