37

我们的编码指南更喜欢const_iterator,因为它们比普通的要快一些iterator。似乎编译器在您使用const_iterator.

这真的正确吗?如果是,那么在内部真正发生了什么使const_iterator速度更快?

编辑:我写了一个小测试来检查const_iteratorvsiterator并发现了不同的结果:

迭代 10,000 个对象所需const_terator的时间减少了几毫秒(大约 16 毫秒)。但并非总是如此。有一些迭代是相等的。

4

11 回答 11

82

如果没有别的,aconst_iterator 读起来更好,因为它告诉任何阅读代码的人“我只是在迭代这个容器,而不是弄乱包含的对象”。

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

于 2009-04-16T09:23:35.563 回答
26

我们使用的指导方针是:

总是更喜欢 const 而不是非常量

如果您倾向于使用 const 对象,那么您会习惯于仅对获得的对象使用常量操作,这与尽可能使用const_iterator一样多。

恒常具有病毒属性。一旦你开始使用它,它就会传播到你的所有代码。您的非变异方法变为常量,并且只需要对属性使用常量操作,并传递常量引用,这本身只强制执行常量操作......

对我来说,使用常量迭代器相对于非常量迭代器(如果有的话)的性能优势远不如代码本身的改进重要。意味着(设计)为非变异的操作恒定的。

于 2009-04-16T11:02:43.133 回答
19

它们适用于重要的容器/迭代器。养成正确的习惯,在重要的时候你就不会失去表现。

此外,无论如何,有几个原因更喜欢 const_iterator:

  1. const 的使用表达了代码意图(即只读,不改变这些对象)。
  2. 使用 const(_iterator) 可以防止意外修改数据。(往上看)
  3. 一些库使用缺少常量begin()来将数据标记为脏数据(即 OpenSG),并将其同步发送到其他线程/通过网络,因此它具有真正的性能影响。
  4. 此外,允许您访问非常量成员函数可能会产生您不希望的副作用(与上述方式大致相同),例如从共享数据中分离写时复制容器。Qt 就是这样做的。

作为上述最后一点的示例,这里是 Qt 中 qmap.h 的摘录:

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

即使 iterator 和 const_iterator 实际上是等效的(除了const), detach()如果有两个或更多对象使用它,也会创建数据的新副本。如果您只是要读取数据,这完全没用,您使用const_iterator.

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

  1. 对于 STL 容器和许多简单复制语义容器,性能无关紧要。代码等效的。但是,能够编写清晰的代码并避免错误会胜出。
  2. const 是病毒式的,因此如果您在 const 实现不佳(或根本没有)实现的遗留代码库中工作,您可能必须使用非 const 迭代器。
  3. 显然,一些 C++0x 之前的 STL 存在一个错误,即 const_iterators 不能用于从容器中擦除()元素。
于 2009-04-16T20:13:32.423 回答
16

我不明白为什么会这样- constness 是编译时检查。但显而易见的答案是编写一个测试。

编辑:这是我的测试 - 它在我的机器上给出了相同的时间:

#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;

}
于 2009-04-16T09:23:39.227 回答
7

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

是的,const_iterator 可能会表现得更好。

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

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

于 2011-04-19T10:43:57.443 回答
6

我们的编码指南说更喜欢 const_iterator

在这里查看 Scott Meyers 的这篇文章。他解释了为什么人们应该更喜欢迭代器而不是 const_iterator。

于 2009-04-16T09:31:26.650 回答
4

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

为了向自己证明没有怪癖,我拿了 anon 的代码,修改为 use clock_gettime,添加了一个外部循环以避免缓存偏差,并运行了很多次。结果出人意料地不一致——上下波动了 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)
于 2011-04-28T03:37:14.380 回答
2

当您对其中任何一个进行基准测试时,请确保使用适当的优化级别——使用“-O0”与“-O”等会得到截然不同的时序。

于 2011-04-28T03:03:03.427 回答
1

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

于 2009-04-16T09:23:24.953 回答
1

与访问限制(公共、受保护、私有)一样,“常量”对程序员的好处多于它对优化的帮助。
由于许多原因(例如 const_cast、可变数据成员、指针/引用别名),编译器实际上无法针对涉及 const 的许多情况进行优化。不过,这里最相关的原因是,仅仅因为 const_iterator 不允许修改它所引用的数据,并不意味着不能通过其他方式更改该数据。如果编译器不能确定数据是只读的,那么它就不能真正优化比非常量迭代器情况更多的优化。
更多信息和示例可以在以下位置找到:http://www.gotw.ca/gotw/081.htm

于 2009-04-16T11:57:31.347 回答
0

根据我的经验,编译器在使用 const 迭代器时不会进行任何可衡量的优化。尽管“它可以”的说法是正确的,并且引用未定义为标准中的指针。

但是,您可以对同一个对象有两个引用,因此一个可以是 const,一个可以是非常量。然后,我猜这个线程中关于限制指针的答案适用:编译器无法知道对象是被另一个线程改变了,还是被一些中断处理代码改变了。

于 2009-04-16T09:30:48.757 回答