我应该优先使用迭代器还是const迭代器?

27

最近有人提到了Scott Meyers的文章,建议:

  • 优先使用迭代器而不是const迭代器 (pdf链接)。

另外一位评论者认为该文章可能已经过时。 我想知道你的想法?

这是我的观点:该文章的主要观点之一是您无法在const迭代器上执行删除或插入操作,但我认为将其作为反对const迭代器的论据很有趣。 我认为const迭代器的整个目的是不对范围进行修改,既不通过替换元素值修改元素本身,也不通过插入或删除修改范围。 或者我漏掉了什么吗?


+1 -- 当我阅读那个讨论时,我也有非常类似的想法。 - Reunanen
3
我开始写一篇回答,想说明你没有理解重点,但后来我意识到梅耶确实说话不太有逻辑。 - Iraimbilanja
1
为了搜索目的,我建议您将作者的名字更正为Scott Meyers。 - Daniel Daranas
1
Meyers先生偏爱iterator的原因是,像std::vector::insert/std::vector::erase这样的方法都需要使用iterator,所以使用其他任何东西(例如const_iterator)都没有好处。但自从C++11以来,这些方法都采用了const_iteratorstd::vector::insertstd::vector::erase),因此现在没有理由优先选择iterator而不是const_iterator。尽可能使用const版本。 - Zizheng Tai
6个回答

22

我完全同意你的观点。 我认为答案很简单: 在需要使用const值时请使用const_iterator,反之亦然。 对我而言,那些反对const_iterator的人一定是反对const的。


9
这里有一种稍微不同的看法。当您将const_iterator作为指针传递到特定集合中并且同时传递了该集合时,几乎从不使用const_iterator。梅耶先生明确指出,const_iterator不能与大多数集合实例的成员函数一起使用。在这种情况下,您需要一个普通的iterator。但是,如果您没有处理集合的句柄,则两者之间唯一的区别在于您可以修改由iterator指向的内容,而无法修改由const_iterator引用的对象。
因此...每当您将集合和位置传递到算法中时,希望使用iterator。基本上,签名如:
void some_operation(std::vector<int>& vec, std::vector::const_iterator pos);

不要过多考虑。隐含的声明是some_operation可以自由修改底层集合,但不允许修改pos所引用的内容。这没有多少意义。如果您真的想要这样做,那么pos应该是一个偏移量而不是迭代器。
另一方面,STL中的大多数算法都基于由一对迭代器指定的范围。集合本身从未被传递,因此iteratorconst_iterator之间的区别在于是否可以通过迭代器修改集合中的值。没有对集合的引用,这种分离非常清晰。
希望这使事情变得清晰明了;)

如果您可以访问容器,例如在一个容器作为类属性的方法中,并且该方法接受const_iterator作为参数,则此方法也适用。 - lothar
在这种情况下,Meyer是正确的,即使该方法是非const的(并且可以通过调用erase来修改集合),您也无法实现该API,因为您无法从包含方法API的const_iterator中获取迭代器(您需要用于erase)。 - lothar
如果您有一个非const版本的集合,您几乎总是可以使用“iter = coll.begin(); std::advance(iter, std::distance(coll.begin(), constIter));”获取可修改的迭代器,尽管语言允许您这样做,但不建议这样做。 - D.Shawley
@D.Shawley:+1,这是一个有洞察力的看法。你说得对,Meyers在这篇文章中似乎忽略了const正确性的整个要点。也就是说,如果你声明一个函数来接受const_iterator参数(而没有一个非const引用容器的参数),调用者知道你不会修改容器。 - j_random_hacker
是的...它甚至比那更明确。按设计,STL不允许通过迭代器获取容器的句柄。因此,唯一可以修改容器的方式是用户显式地提供对其的访问权限。当然,如果您向迭代器提供对象已经拥有容器,这种情况就会有所改变,正如本主题中其他人所提到的那样。 - D.Shawley

4

我通常更喜欢使用const,但最近在处理const_iterators时遇到了一个难题,这让我对“尽可能使用const”的理念感到困惑:

MyList::const_iterator find( const MyList & list, int identifier )
{
    // do some stuff to find identifier
    return retConstItor;
}

既然传递const列表引用需要我只使用const迭代器,现在如果我使用find,我除了查看结果之外什么都不能做,即使我只想表达find不会改变被传入的列表。

我想知道,Scott Mayers的建议是否与这样的问题有关,即无法摆脱const性质。据我所知,您不能(可靠地)通过简单的转换取消const_iterators的const属性,因为涉及到一些内部细节。这也可能是问题所在。

这可能与此相关:如何去除const_iterator的const属性?


"我无法对结果做任何处理",这就是const的意义。如果您更改数据,就会更改列表。 - GManNickG
在这种情况下,const语义上仅表示“此函数不会修改列表”。问题并不是const本身,而是const_iterator的概念。如果您可以访问列表的非const版本,则应该能够使用(const)iterator来修改指向该列表上的项目的指针。从技术上讲,您可以这样做,但不能保证它是一个常数时间操作,而它应该是。就我而言,所有(非const)stl容器都应该有一种将cont_iterator转换为非const iterator的方法(在常数时间内)。 - Catskul
如果函数返回一个可变迭代器,它提供了一种改变列表的方法。 - GManNickG
丑陋的解决方案是提供两个版本的函数,一个接受const引用并返回const_iterator,另一个接受非const引用并返回iterator。任何返回非const指针、引用或迭代器的函数都应该接受非const参数,即使它不改变对象本身。 - Mark Ransom
1
我应该补充一下,自从C++11以来,这个问题已经得到解决。insert/erase等现在都接受const_iterator - Zizheng Tai
显示剩余3条评论

3

C++98

我认为我们需要考虑Meyers的陈述是关于C++98的。虽然现在很难确定,但如果我没记错:

  • 获取非const容器的const_iterator根本不容易
  • 即使你获得了const_iterator,你也几乎无法使用它,因为大多数(全部?)容器成员函数的位置参数都期望是迭代器而不是const_iterator

例如:

std::vector<int> container;

would have required

static_cast<std::vector<int>::const_iterator>(container.begin())

要获得一个const_iterator,这将极大地增加简单的.find操作的复杂性,即使你得到了结果,在此之后。
std::vector<int>::const_iterator i = std::find(static_cast<std::vector<int>::const_iterator>(container.begin()), static_cast<std::vector<int>::const_iterator>(container.end()),42);

在以前,使用std::vector::const_iterator无法将元素插入到向量或任何其他希望迭代器作为位置的成员函数中。并且无法从const iterator获得迭代器。没有任何方式可以进行类型转换。
因为const iterator并不意味着容器不能被更改,而只是指所指向的元素不能被更改(const iterator相当于指向const的指针),这在这种情况下真的很难处理。
今天情况与过去相反。即使对于非const容器,使用cbegin等也很容易获取const iterator,并且所有(?)接受位置参数的成员函数都将const iterator作为它们的参数,因此不需要进行任何转换。
std::vector<int> container;                
auto i = std::find(container.cbegin(), container.cend(), 42); 
container.insert(i, 43); 

因为第一个优选迭代器而不是const_iterators只是历史实现缺陷的产物。

今天,真的真的应该优先选择const_iterators而不是迭代器


C++98 中 const 迭代器的真正问题在于容器自身的方法不接受它们。你想要删除元素,但你只有一个 const_iterator?那就太糟糕了,因为当时 erase 只接受 iterator。想在该元素后插入元素?找到一种将其转换为 iterator 的方法吧。因此,const 迭代器基本上是无用的。 - Revolver_Ocelot

3

我认为Meyer的这个陈述并不需要特别关注。当您需要进行非修改操作时,最好使用const_iterator。否则,请使用普通的iterator。但是,请注意一件重要的事情:永远不要混合使用迭代器,即constnon-const迭代器。只要您意识到后者,就应该没问题。


2
根据我阅读的链接,Meyers似乎基本上是在说,迭代器比const_iterator更好,因为您无法通过const_iterator进行更改。
但如果他是这样说的,那么Meyers实际上是错误的。当您想要表达这一点时,正是const_iterator比iterator更好的原因。

问题在于,如果您有一个const_iterator和一个非const容器。您应该能够使用const_iterator指示容器要更改的位置,因为容器本身不是const,但您不能这样做。看看Catskul的查找示例 - 它应该接受一个const容器并返回const_iterator - 但是,即使您拥有的容器不是const,您也无法从容器中删除找到的内容。 - Bjarke H. Roune

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