cbegin / cend背后的原因是什么?


Answers:


227

这很简单。假设我有一个向量:

std::vector<int> vec;

我用一些数据填充它。然后,我想获得一些迭代器。也许把它们传过去。也许去std::for_each

std::for_each(vec.begin(), vec.end(), SomeFunctor());

在C ++ 03中,SomeFunctor可以随意修改它获取的参数。当然,SomeFunctor可以按值或按接受其参数const&,但是无法确保它可以做到。并非没有做过这样愚蠢的事情:

const std::vector<int> &vec_ref = vec;
std::for_each(vec_ref.begin(), vec_ref.end(), SomeFunctor());

现在,我们介​​绍cbegin/cend

std::for_each(vec.cbegin(), vec.cend(), SomeFunctor());

现在,我们有了语法保证,SomeFunctor无法修改向量的元素(当然,没有const-cast)。我们显式获得const_iterators,因此SomeFunctor::operator()将被调用const int &。如果将其参数设为int &,则C ++将发出编译器错误。


C ++ 17对这个问题有一个更优雅的解决方案:std::as_const。好吧,至少在使用基于范围的方法时很优雅for

for(auto &item : std::as_const(vec))

这只是将a返回const&给它提供的对象。


1
我认为新协议是cbegin(vec)而不是vec.cbegin()。
卡兹龙

20
@Kaz:没有std::cbegin/cend免费函数std::begin/std::end存在。这是委员会的监督。如果确实存在这些功能,那通常就是使用它们的方式。
尼科尔·波拉斯

20
显然,std::cbegin/cend将在C ++ 14中添加。参见en.cppreference.com/w/cpp/iterator/begin
Adi Shavit

8
@NicolBolas for(auto &item : std::as_const(vec))相当于for(const auto &item : vec)什么?
luizfls

8
@luizfls是的。您的代码说,将const引用放在该项目上不会对其进行修改。Nicol将容器视为const,因此auto推论出一个const引用。IMO auto const& item更加容易和清晰。目前尚不清楚为什么std::as_const()这里很好。我可以看到,当将一些非非const通用代码传递给我们无法控制要使用的类型的通用代码时,这很有用,但是有了range- for,我们可以了,所以这似乎给我增加了噪音。
underscore_d

66

除了Nicol Bolas在回答中所说的以外,请考虑以下新auto关键字:

auto iterator = container.begin();

使用auto,无法确保begin()为非常数容器引用返回常数运算符。所以现在您要做:

auto const_iterator = container.cbegin();

2
@allyourcode:没有帮助。对于编译器,const_iterator只是另一个标识符。这两个版本均未使用通常的成员typedefs decltype(container)::iterator或查找decltype(container)::const_iterator
aschepler 2013年

2
@aschepler我不明白您的第二句话,但是我认为您在我的问题中错过了“ auto”前面的“ const”。不管自动涉及到什么,似乎const_iterator应该是const。
allyourcode 2013年

26
@allyourcode:这将为您提供一个常量的迭代器,但是从迭代器到常量数据却大不相同。
aschepler 2013年

2
有一种简单的方法可确保您获得const_iteratorwith auto:编写一个称为make_const对对象参数进行限定的辅助函数模板。
哥伦布2015年

17
也许我不再处于C ++思维方式中,但是我看不到“简单方式”和“编写辅助功能模板”之间的联系。;)
Stefan Majewsky

15

将此作为实际用例

void SomeClass::f(const vector<int>& a) {
  auto it = someNonConstMemberVector.begin();
  ...
  it = a.begin();
  ...
}

分配失败,因为它it是一个非常量迭代器。如果最初使用cbegin,则迭代器将具有正确的类型。


8

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1674.pdf

这样程序员甚至可以从非const容器中直接获取const_iterator

他们举了这个例子

vector<MyType> v;

// fill v ...
typedef vector<MyType>::iterator iter;
for( iter it = v.begin(); it != v.end(); ++it ) {
    // use *it ...
}

但是,当容器遍历仅用于检查时,通常首选做法是使用const_iterator,以允许编译器诊断出const正确性违规。

请注意,工作文件中还提到了适配器模板,该模板现在已经以std::begin()和形式完成,并且std::end()还可以用于本机阵列。到目前为止,相应的std::cbegin()std::cend()奇怪地丢失了,但是也可以添加它们。


5

只是偶然发现了这个问题...我知道它已经被回答了,而且只是一个副节点...

auto const it = container.begin() 然后是另一种类型 auto it = container.cbegin()

的区别int[5](使用指针,我知道没有begin方法,但是很好地显示了区别...但是可以在c ++ 14中用于std::cbegin()std::cend(),这基本上是在这里时应该使用的指针)...

int numbers = array[7];
const auto it = begin(numbers); // type is int* const -> pointer is const
auto it = cbegin(numbers);      // type is int const* -> value is const

2

iterator并且const_iterator具有继承关系,并且与其他类型进行比较或分配给其他类型时,会发生隐式转换。

class T {} MyT1, MyT2, MyT3;
std::vector<T> MyVector = {MyT1, MyT2, MyT3};
for (std::vector<T>::const_iterator it=MyVector.begin(); it!=MyVector.end(); ++it)
{
    // ...
}

在这种情况下,使用cbegin()cend()将提高性能。

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}

我花了一段时间才意识到您的意思是通过在初始化和比较迭代器时避免转换来节省性能,而不是流行的神话const(即,它不是语义上正确和安全的代码),它的主要好处是性能。但是,尽管您有观点,但(A)auto认为这不是问题;(B)在谈论性能时,您错过了这里应该做的主要事情:endfor循环的init-condition中声明它的一个副本来缓存迭代器,并与之进行比较,而不是通过每次迭代的价值。那会让你的观点更好。:P
underscore_d

@underscore_d const绝对可以帮助获得更好的性能,这不是因为const关键字本身有魔术,而是因为如果编译器知道不会修改数据,则可以启用某些优化,否则将无法实现。请从杰森·特纳(Jason Turner)的演讲中查看这一点,以获取有关此示例的实时示例。
brainplot

@brainplot我没有说不能。我说这不是它的主要好处,并且当它的真正好处是语义正确和安全的代码时,我认为它被夸大了。
underscore_d

@underscore_d是的,我同意这一点。我只是在明确指出const可以(几乎间接)带来性能收益。以防万一有人读到这篇文章可能会认为“ const如果生成的代码从未受到任何影响,我不会费心添加”,这是不正确的。
brainplot

0

简单来说,cbegin返回一个常量迭代器,而begin仅返回一个迭代器

为了更好地理解,我们在这里采取两种情况

场景-1:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.begin();i< v.end();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

这将运行,因为这里的迭代器i不是恒定的,可以递增5

现在让我们使用cbegin并将cend表示为常量迭代器场景-2:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.cbegin();i< v.cend();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

这是行不通的,因为您不能使用cbegin和cend更新值,该值将返回常量迭代器

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.