C++ STL中const_iterator和non-const iterator有什么区别?

175

const_iterator 和 iterator 有什么区别,使用其中一个要比另一个更好的情况是什么?


4
const_iterator 这个名称听起来像是迭代器是 const 的,而实际上这个迭代器指向的才是真正的 const。 - talekeDskobeDa
7个回答

158

const_iterator 不允许您更改它们指向的值,而普通的 iterator 则允许。

就像C++中的所有事物一样,始终优先使用 const,除非有充分的理由使用常规迭代器(例如,您想利用它们不是 const 来更改指向的值)。


10
在完美的世界里会是这样的。但是在C++中,const仅取决于编写代码的人的水平 :( - JaredPar
3
更像是一个强力的hack。我所见过使用'mutable'关键字的所有实例中,除了一个例外之外,都准确地表明代码编写不良,需要使用'mutable'来绕过缺陷。 - John Dibling
7
它确实有合法的用途,比如在const类中缓存长时间计算的结果。另一方面,在我近20年的C++开发中,这几乎是我唯一使用mutable的时候。 - Head Geek
@schrödinbug 为什么一个线程会改变一个const对象?这不是又一个糟糕设计的例子吗?请原谅,我只是真心想问一下。 - Max
如果您有一个在两个或多个线程之间共享的对象。一个线程是写入者,其他线程只是通过获取器(getter)读取值(通过锁定互斥量),以便制作其本地副本。根据对象实例化的位置,所有读取器线程可能只有对该对象的const &s。然而,鉴于共享状态是所有多线程恶魔的根源,您的陈述可能是正确的。 - schrödinbug
显示剩余3条评论

49

它们应该基本上是自解释的。如果迭代器指向类型为T的元素,则const_iterator指向类型为“const T”的元素。

这基本上相当于指针类型:

T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator

const迭代器总是指向同一个元素,因此迭代器本身是常量。但它所指向的元素不一定是常量,因此它所指向的元素可以被改变。 const_iterator是一个指向const元素的迭代器,因此虽然迭代器本身可以更新(例如增加或减少),但它所指向的元素不能被更改。


2
“一个const迭代器总是指向同一个元素”这种说法是不正确的。 - John Dibling
3
怎么样?注意下划线缺失。我在将类型为const std::vector<T>::iterator的变量与std::vector<T>::const_iterator进行对比。 在前一种情况下,迭代器本身是const的,因此无法修改它,但它所引用的元素可以自由地修改。 - jalf
4
啊,我明白了。是的,我漏掉了那个下划线。 - John Dibling
5
因为 @JohnDibling 解释了 const iteratorconst_iterator 之间的微妙差别,所以我点了赞。 - legends2k

9

最小化的可运行示例

非const迭代器允许你修改它们所指向的内容:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);

常量迭代器不会:

const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

如上所示,v.begin() 进行了 const 重载,并根据容器变量的常数性返回 iteratorconst_iterator

当在 const 方法中使用 this 时,const_iterator 是一个常见的情况:

class C {
    public:
        std::vector<int> v;
        void f() const {
            std::vector<int>::const_iterator it = this->v.begin();
        }
        void g(std::vector<int>::const_iterator& it) {}
};

const 使 this 变为常量,从而使 this->v 变成常量。

通常可以使用 auto 简化代码,但是如果开始传递这些迭代器,则需要考虑它们在方法签名中的使用。

与 const 和非 const 类似,可以轻松地从非 const 转换为 const,但反过来则不行:

std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();

// non-const to const.
std::vector<int>::const_iterator cit = it;

// Compile time error: cannot modify container with const_iterator.
//*cit = 1;

// Compile time error: no conversion from const to no-const.
//it = ci1;

该使用哪个:类比于 const intint:尽可能使用 const 迭代器(当您不需要使用它们修改容器时),以更好地记录您的读取而不是修改的意图。


8

不幸的是,STL容器的许多方法都采用迭代器而不是const_iterators作为参数。因此,如果您有一个const_iterator,则无法说“在此迭代器指向的元素之前插入一个元素”(在我看来,这样说并不是概念上的const违规)。如果您仍想要这样做,则必须使用std :: advance()或boost :: next()将其转换为非const迭代器。例如,boost :: next(container.begin(),std :: distance(container.begin(),the_const_iterator_we_want_to_unconst))。如果container是std :: list,则该调用的运行时间为O(n)。

因此,在添加const的通常适用的"逻辑"位置时,请注意STL容器的例外情况。

然而,boost容器采用const_iterators(例如boost :: unordered_map :: erase())。因此,在使用boost容器时,可以更加"const agressive"。顺便问一下,有人知道STL容器何时会被修复吗?


1
可能是个人观点问题。在 vectordeque 的情况下,插入一个元素会使所有现有的迭代器失效,这并不是很符合 const 的概念。但我确实理解你的观点。这些操作受到容器的 const 属性保护,而不是迭代器。我也想知道为什么标准容器接口中没有 const 到非 const 迭代器的转换函数。 - Potatoswatter
你是正确的Potatoswatter,我太过于细分类了,对于随机访问容器和container.begin() + (the_const_iterator_we_want_to_unconst - container.begin())无论如何都是O(1)。我也想知道为什么非随机访问容器没有转换函数,但也许有一个很好的理由?你知道非随机访问容器的函数为什么不使用const_iterators吗? - Magnus Andermo
“在我看来,这样说并不会违反常量的概念。” 这是一个非常有趣的评论,我对这个话题有以下想法。通过简单的指针,我们可以说 int const * foo;int * const foo;int const * const foo; 三者都是有效和有用的,每个都有自己的用途。std::vector<int> const bar 应该与第二个相同,但不幸的是它经常被当作第三个来处理。问题的根本原因是我们无法说 std::vector<int const> bar;,这意味着在向量中没有办法获得与 int const *foo; 相同的效果。 - dgnuff

7

尽可能使用 const_iterator,当没有其他选择时才使用 iterator


0

好的,让我先用一个非常简单的例子来解释一下,不使用常量迭代器。假设我们有一个随机整数集合"randomData"。

    for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;

可以看到,在集合内编写/编辑数据时使用普通迭代器,但是为了阅读目的,使用了常量迭代器。 如果你尝试在第一个for循环中使用常量迭代器,将会出现错误。 作为一个经验法则,请使用常量迭代器来读取集合内的数据。

0
(正如其他人所说)const_iterator不允许您修改其所指向的元素,这在const类方法内部非常有用。它还允许您表达自己的意图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接