Qual è la ragione dietro cbegin / cend?


Risposte:


228

È abbastanza semplice. Di 'che ho un vettore:

std::vector<int> vec;

Lo riempio con alcuni dati. Quindi voglio ottenere alcuni iteratori. Forse passarli in giro. Forse per std::for_each:

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

In C ++ 03, SomeFunctorera libero di poter modificare il parametro che ottiene. Certo, SomeFunctorpotrebbe prendere il suo parametro per valore o per const&, ma non c'è modo di assicurarselo . Non senza fare qualcosa di stupido come questo:

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

Ora presentiamo cbegin/cend:

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

Ora, abbiamo assicurazioni sintattiche che SomeFunctornon possono modificare gli elementi del vettore (senza un const-cast, ovviamente). Riceviamo esplicitamente const_iterators, e quindi SomeFunctor::operator()verremo chiamati con const int &. Se accetta i parametri come int &, C ++ genererà un errore del compilatore.


C ++ 17 ha una soluzione più elegante a questo problema: std::as_const. Bene, almeno è elegante quando si usa il range-based for:

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

Ciò restituisce semplicemente const&a l'oggetto che viene fornito.


1
Ho pensato che il nuovo protocollo fosse cbegin (vec) piuttosto che vec.cbegin ().
Kaz Dragon,

20
@Kaz: non esistono std::cbegin/cendfunzioni libere nel modo in cui std::begin/std::endesistono. È stata una svista da parte del comitato. Se tali funzioni esistessero, quello sarebbe generalmente il modo di usarle.
Nicol Bolas,

20
Apparentemente, std::cbegin/cendverrà aggiunto in C ++ 14. Vedi en.cppreference.com/w/cpp/iterator/begin
Adi Shavit

9
@NicolBolas è for(auto &item : std::as_const(vec))equivalente a for(const auto &item : vec)?
luizfls,

9
@luizfls Sì. Il tuo codice dice che l'articolo non verrà modificato inserendo constil riferimento. Nicol considera il contenitore come const, quindi autodeduce un constriferimento. L'IMO auto const& itemè più semplice e chiaro. Non è chiaro perché std::as_const()qui va bene; Vedo che sarebbe utile quando si passa qualcosa di non constgenerico a codice in cui non possiamo controllare il tipo che viene utilizzato, ma con range- for, possiamo, quindi mi sembra solo un rumore aggiunto lì.
underscore_d

66

Oltre a ciò che ha detto Nicol Bolas nella sua risposta , considera la nuova autoparola chiave:

auto iterator = container.begin();

Con auto, non c'è modo di assicurarsi che begin()restituisca un operatore costante per un riferimento contenitore non costante. Quindi ora fai:

auto const_iterator = container.cbegin();

2
@allyourcode: non aiuta. Per il compilatore, const_iteratorè solo un altro identificatore. Nessuna versione utilizza una ricerca dei soliti typedef dei membri decltype(container)::iteratoro decltype(container)::const_iterator.
aschepler,

2
@aschepler Non capisco la tua seconda frase, ma penso che tu abbia perso la "const" di fronte a "auto" nella mia domanda. Qualunque sia l'auto, sembra che const_iterator dovrebbe essere const.
codice alleato

26
@allyourcode: questo ti darebbe un iteratore che è costante, ma che è molto diverso da un iteratore a dati costanti.
aschepler,

2
C'è un modo semplice per assicurarti di ottenere un const_iteratorcon auto: Scrivi un modello di funzione ausiliaria chiamato make_constper qualificare l'argomento oggetto.
Columbo,

17
Forse non sono più nella mentalità C ++, ma non riesco a vedere una connessione tra i concetti di "un modo semplice" e "scrivere un modello di funzione ausiliaria". ;)
Stefan Majewsky,

15

Prendi questo come un pratico caso d'uso

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

L'assegnazione non riesce perché itè un iteratore non costante. Se inizialmente avessi usato cbegin, l'iteratore avrebbe avuto il tipo giusto.


8

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

in modo che un programmatore possa ottenere direttamente un const_iterator anche da un contenitore non const

Hanno dato questo esempio

vector<MyType> v;

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

Tuttavia, quando un attraversamento di container è destinato solo a ispezione, è generalmente preferibile utilizzare un const_iterator per consentire al compilatore di diagnosticare le violazioni della correttezza della const

Si noti che il documento di lavoro menziona anche adattatori modelli, che ora sono stati finalizzati, come std::begin()e std::end(), e che anche il lavoro con gli array native. I corrispondenti std::cbegin()e std::cend()sono curiosamente mancanti al momento, ma potrebbero anche essere aggiunti.


5

Mi sono appena imbattuto in questa domanda ... So che è Alredy answerd ed è solo un nodo laterale ...

auto const it = container.begin() è un tipo diverso quindi auto it = container.cbegin()

la differenza per int[5](usando il puntatore, che conosco non ha il metodo iniziale ma mostra bene la differenza ... ma funzionerebbe in c ++ 14 per std::cbegin()e std::cend(), che è essenzialmente quello che si dovrebbe usare quando è qui) ...

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

iteratore const_iteratorhanno una relazione di ereditarietà e si verifica una conversione implicita se confrontata con o assegnata all'altro 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)
{
    // ...
}

Utilizzando cbegin()e cend()aumenterà le prestazioni in questo caso.

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

Mi ci è voluto un po 'di tempo per capire che intendevi salvare le prestazioni evitando la conversione durante l'inizializzazione e il confronto degli iteratori, non il mito popolare che constil principale vantaggio è la prestazione (cosa che non è: è un codice semanticamente corretto e sicuro). Ma, mentre hai un punto, (A) lo autorende un problema; (B) parlando delle prestazioni, ti sei perso una cosa principale che avresti dovuto fare qui: memorizza nella cache l' enditeratore dichiarandone una copia nella condizione init del forciclo, e confrontalo con quello, invece di ottenere una nuova copia da valore per ogni iterazione. Ciò renderà il tuo punto migliore. : P
underscore_d

@underscore_d constpuò sicuramente aiutare a ottenere prestazioni migliori, non a causa della magia nella constparola chiave stessa, ma perché il compilatore può abilitare alcune ottimizzazioni se sa che i dati non verranno modificati, altrimenti non sarebbe possibile. Dai un'occhiata a questo pezzo del discorso di Jason Turner per un esempio dal vivo di questo.
brainplot

@brainplot Non ho detto che non poteva. Ho detto che non è il suo principale vantaggio e che penso che sia sopravvalutato, quando il vero vantaggio è un codice semanticamente corretto e sicuro.
underscore_d

@underscore_d Sì, sono d'accordo. Stavo solo rendendo esplicito che constpuò (quasi indirettamente) portare a benefici in termini di prestazioni; nel caso qualcuno che legga questo potrebbe pensare "Non mi preoccuperò di aggiungere constse il codice generato non è influenzato in alcun modo", il che non è vero.
brainplot

0

è semplice, cbegin restituisce un iteratore costante in cui inizio restituisce solo un iteratore

per una migliore comprensione, prendiamo due scenari qui

scenario 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;
}

questo funzionerà perché qui iteratore i non è costante e può essere incrementato di 5

ora usiamo cbegin e cend denotandoli come scenario di iteratori costanti - 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;
}

questo non funzionerà, perché non puoi aggiornare il valore usando cbegin e cend che restituisce l'iteratore costante

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.