Qual é a razão por trás de cbegin / cend?

187

Gostaria de saber por que cbegine cendforam introduzidos no C ++ 11?

Quais são os casos em que chamar esses métodos faz diferença das sobrecargas const de begine end?

Andrey
fonte

Respostas:

226

É bem simples Digamos que eu tenha um vetor:

std::vector<int> vec;

Eu preencho com alguns dados. Então eu quero obter alguns iteradores para ele. Talvez passá-los por aí. Talvez para std::for_each:

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

No C ++ 03, SomeFunctorestava livre para poder modificar o parâmetro obtido. Claro, SomeFunctorpoderia pegar seu parâmetro por valor ou por const&, mas não há como garantir isso. Não sem fazer algo bobo como este:

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

Agora, apresentamos cbegin/cend:

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

Agora, temos garantias sintáticas que SomeFunctornão podem modificar os elementos do vetor (sem const-cast, é claro). Nós obtemos explicitamente const_iterators e, portanto SomeFunctor::operator(), seremos chamados com const int &. Se for necessário como parâmetros int &, o C ++ emitirá um erro do compilador.


C ++ 17 tem uma solução mais elegante para este problema: std::as_const. Bem, pelo menos é elegante ao usar o intervalo for:

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

Isso simplesmente retorna const&a ao objeto que é fornecido.

Nicol Bolas
fonte
1
Eu pensei que o novo protocolo fosse cbegin (vec) em vez de vec.cbegin ().
quer
20
@ Kaz: Não existem std::cbegin/cendfunções gratuitas da maneira que std::begin/std::endexistem. Foi uma supervisão do comitê. Se essas funções existissem, essa seria geralmente a maneira de usá-las.
Nicol Bolas
20
Aparentemente, std::cbegin/cendserá adicionado no C ++ 14. Veja en.cppreference.com/w/cpp/iterator/begin
Adi Shavit
8
@NicolBolas é for(auto &item : std::as_const(vec))equivalente a for(const auto &item : vec)?
luizfls
8
@luizfls Sim. Seu código diz que o item não será modificado colocando-o constna referência. Nicol vê o contêiner como const, portanto autodeduz uma constreferência. OMI auto const& itemé mais fácil e mais claro. Não está claro por que std::as_const()é bom aqui; Percebo que seria útil passar algo não constgenérico em código onde não podemos controlar o tipo que é usado, mas com range- for, podemos, por isso parece um ruído adicional para mim.
underscore_d
66

Além do que Nicol Bolas disse em sua resposta , considere a nova autopalavra-chave:

auto iterator = container.begin();

Com auto, não há como garantir que begin()retorne um operador constante para uma referência de contêiner não constante. Então agora você faz:

auto const_iterator = container.cbegin();
Stefan Majewsky
fonte
2
@allourcode: não ajuda. Para o compilador, const_iteratoré apenas outro identificador. Nenhuma versão usa uma pesquisa dos membros usuais typedefs decltype(container)::iteratorou decltype(container)::const_iterator.
aschepler
2
@aschepler Eu não entendo sua segunda frase, mas acho que você perdeu o "const" na frente de "auto" na minha pergunta. Qualquer que seja o auto, parece que const_iterator deve ser const.
Allyourcode
26
@allyourcode: Isso daria a você um iterador constante, mas muito diferente de um iterador para dados constantes.
Aschepler
2
Há uma maneira simples de garantir que você obtenha um const_iteratorcom auto: Escrever um modelo de função auxiliar chamada make_constpara qualificar o argumento de objeto.
Columbo
17
Talvez eu não esteja mais na mentalidade de C ++, mas não consigo ver uma conexão entre os conceitos de "uma maneira simples" e "escrever um modelo de função auxiliar". ;)
Stefan Majewsky
15

Tome isso como um caso prático

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

A atribuição falha porque ité um iterador não constante. Se você usou cbegin inicialmente, o iterador teria o tipo certo.

Johannes Schaub - litb
fonte
8

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

para que um programador possa obter diretamente um const_iterator mesmo de um contêiner não-const

Eles deram este exemplo

vector<MyType> v;

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

No entanto, quando uma travessia de contêiner é destinada apenas à inspeção, é geralmente uma prática preferida usar um const_iterator para permitir que o compilador diagnostique violações da correção da const

Note-se que o documento de trabalho também menciona adaptador modelos, que agora foram finalizadas como std::begin()e std::end()e que também trabalham com matrizes nativas. Os correspondentes std::cbegin()e std::cend()estão curiosamente ausentes a partir deste momento, mas eles também podem ser adicionados.

TemplateRex
fonte
5

Só me deparei com esta pergunta ... Eu sei que já está respondida e é apenas um nó lateral ...

auto const it = container.begin() é um tipo diferente então auto it = container.cbegin()

A diferença para int[5](usando ponteiro, que eu sei que não tem o método begin, mas mostra bem a diferença ... mas funcionaria em c ++ 14 para std::cbegin()e std::cend(), que é essencialmente o que se deve usar quando estiver aqui) ...

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
chris g.
fonte
2

iteratore const_iteratorter um relacionamento de herança e ocorre uma conversão implícita quando comparada com ou atribuída ao outro tipo.

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)
{
    // ...
}

Usando cbegin()e cend()aumentará o desempenho neste caso.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
fonte
Demorei um pouco para perceber que você queria dizer que o desempenho é salvo evitando a conversão ao inicializar e comparar iteradores, não o mito popular de que consto principal benefício é o desempenho (o que não é: é um código semanticamente correto e seguro). Mas, enquanto você tem um argumento, (A) autotorna isso um problema; (B) ao falar sobre desempenho, você perdeu uma das coisas principais que deveria ter feito aqui: armazenar em cache o enditerador declarando uma cópia dele na condição init do forloop, e comparar com isso, em vez de obter uma nova cópia valor para cada iteração. Isso fará com que o seu ponto seja melhor. : P
underscore_d
O @underscore_d constpode definitivamente ajudar a obter um melhor desempenho, não por causa de alguma mágica na constprópria palavra-chave, mas porque o compilador pode ativar algumas otimizações se souber que os dados não serão modificados, o que não seria possível de outra forma. Confira este trecho da palestra de Jason Turner para um exemplo ao vivo disso.
brainplot
@brainplot Eu não disse que não podia. Eu disse que esse não é seu principal benefício e que acho que é exagerado, quando o verdadeiro benefício é um código semanticamente correto e seguro.
Underscore_d
@underscore_d Sim, eu concordo com isso. Eu estava apenas explicitando que constpode (quase indiretamente) levar a benefícios de desempenho; apenas no caso de alguém que esteja lendo isso possa pensar "Não vou me incomodar em adicionar constse o código gerado não for afetado de forma alguma" ", o que não é verdade.
brainplot
0

simples, cbegin retorna um iterador constante, onde begin retorna apenas um iterador

para uma melhor compreensão, vamos dar dois cenários aqui

Cenário 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;
}

isso será executado porque aqui o iterador i não é constante e pode ser incrementado em 5

agora vamos usar cbegin e cend denotando-os como cenário de iteradores constantes - 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;
}

isso não vai funcionar, porque você não pode atualizar o valor usando cbegin e cend, que retorna o iterador constante

PAVAN KUMAR
fonte