Quelle est la raison derrière cbegin / cend?

189

Je me demande pourquoi cbeginet cendont été introduits dans C ++ 11?

Quels sont les cas où l'appel de ces méthodes fait une différence par rapport aux surcharges const de beginet end?

Andreï
la source

Réponses:

228

C'est assez simple. Disons que j'ai un vecteur:

std::vector<int> vec;

Je le remplis de quelques données. Ensuite, je veux y trouver des itérateurs. Peut-être les faire circuler. Peut-être pour std::for_each:

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

En C ++ 03, SomeFunctorétait libre de pouvoir modifier le paramètre qu'il obtient. Bien sûr, SomeFunctorpourrait prendre son paramètre par valeur ou par const&, mais il n'y a aucun moyen de garantir que c'est le cas. Non sans faire quelque chose de stupide comme ça:

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

Maintenant, nous introduisons cbegin/cend:

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

Maintenant, nous avons des assurances syntaxiques qui SomeFunctorne peuvent pas modifier les éléments du vecteur (sans const-cast, bien sûr). Nous obtenons explicitement const_iterators, et nous SomeFunctor::operator()serons donc appelés avec const int &. S'il prend ses paramètres comme int &, C ++ émettra une erreur de compilation.


C ++ 17 a une solution plus élégante à ce problème: std::as_const. Eh bien, au moins, c'est élégant lors de l'utilisation de la plage for:

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

Cela renvoie simplement un const&à l'objet qui lui est fourni.

Nicol Bolas
la source
1
Je pensais que le nouveau protocole était cbegin (vec) plutôt que vec.cbegin ().
Kaz Dragon
20
@Kaz: Il n'y a pas de std::cbegin/cendfonctions gratuites telles qu'elles std::begin/std::endexistent. C'était une erreur du comité. Si ces fonctions existaient, ce serait généralement la manière de les utiliser.
Nicol Bolas
20
Apparemment, std::cbegin/cendsera ajouté en C ++ 14. Voir en.cppreference.com/w/cpp/iterator/begin
Adi Shavit
9
@NicolBolas for(auto &item : std::as_const(vec))équivaut à for(const auto &item : vec)?
luizfls
9
@luizfls Oui. Votre code indique que l'élément ne sera pas modifié en mettant le constsur la référence. Nicol's considère le conteneur comme const, donc en autodéduit une constréférence. L'OMI auto const& itemest plus simple et plus claire. On ne sait pas pourquoi std::as_const()c'est bon ici; Je peux voir que ce serait utile lors du passage de quelque chose de non- constau code générique où nous ne pouvons pas contrôler le type qui est utilisé, mais avec range- for, nous pouvons, donc cela me semble juste comme du bruit supplémentaire.
underscore_d
66

Au-delà de ce que Nicol Bolas a dit dans sa réponse , considérons le nouveau automot-clé:

auto iterator = container.begin();

Avec auto, il n'y a aucun moyen de s'assurer que begin()renvoie un opérateur constant pour une référence de conteneur non constante. Alors maintenant vous faites:

auto const_iterator = container.cbegin();
Stefan Majewsky
la source
2
@allyourcode: n'aide pas. Pour le compilateur, const_iteratorc'est juste un autre identifiant. Aucune des deux versions n'utilise une recherche des typedefs de membres habituels decltype(container)::iteratorou decltype(container)::const_iterator.
aschepler
2
@aschepler Je ne comprends pas votre deuxième phrase, mais je pense que vous avez manqué le "const" devant "auto" dans ma question. Quel que soit le résultat de l'auto, il semble que const_iterator devrait être const.
allyourcode
26
@allyourcode: Cela vous donnerait un itérateur qui est constant, mais qui est très différent d'un itérateur à des données constantes.
aschepler
2
Il existe un moyen simple de s'assurer que vous obtenez un const_iteratoravec auto: Ecrivez un modèle de fonction auxiliaire appelé make_constpour qualifier l'argument objet.
Columbo
17
Peut-être que je ne suis tout simplement plus dans la mentalité C ++, mais je ne vois pas de lien entre les concepts de «manière simple» et «d'écrire un modèle de fonction auxiliaire». ;)
Stefan Majewsky
15

Prenez ceci comme un cas d'utilisation pratique

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

L'affectation échoue car il its'agit d'un itérateur non constant. Si vous avez utilisé cbegin au départ, l'itérateur aurait eu le bon type.

Johannes Schaub - litb
la source
8

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

afin qu'un programmeur puisse directement obtenir un const_iterator à partir même d'un conteneur non-const

Ils ont donné cet exemple

vector<MyType> v;

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

Cependant, lorsqu'une traversée de conteneur est destinée à l'inspection uniquement, il est généralement préférable d'utiliser un const_iterator afin de permettre au compilateur de diagnostiquer les violations de const-correctness

Notez que le document de travail mentionne également les modèles d'adaptateur, qui ont maintenant été finalisés au fur std::begin()et à mesure std::end()et qui fonctionnent également avec des tableaux natifs. Les correspondants std::cbegin()et std::cend()sont curieusement manquants à partir de ce moment, mais ils pourraient également être ajoutés.

TemplateRex
la source
5

Je suis juste tombé sur cette question ... Je sais que c'est déjà répondu et c'est juste un nœud secondaire ...

auto const it = container.begin() est un type différent alors auto it = container.cbegin()

la différence pour int[5](en utilisant un pointeur, qui, je sais, n'a pas la méthode begin mais montre bien la différence ... mais fonctionnerait en c ++ 14 pour std::cbegin()et std::cend(), qui est essentiellement ce que l'on devrait utiliser quand il est ici) ...

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.
la source
2

iteratoret const_iteratoront une relation d'héritage et une conversion implicite se produit lors de la comparaison ou de l'affectation à l'autre type.

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

L'utilisation de cbegin()et cend()augmentera les performances dans ce cas.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
la source
Il m'a fallu un certain temps pour réaliser que vous vouliez dire que les performances sont sauvegardées en évitant la conversion lors de l'initialisation et de la comparaison des itérateurs, et non le mythe populaire dont constle principal avantage est la performance (ce qui n'est pas le cas: c'est un code sémantiquement correct et sûr). Mais, pendant que vous avez un point, (A) en autofait un non-problème; (B) en parlant de performances, vous avez manqué une chose principale que vous auriez dû faire ici: mettre en cache l' enditérateur en déclarant une copie de celui-ci dans la condition d'initialisation de la forboucle, et comparer à cela, au lieu d'obtenir une nouvelle copie par valeur pour chaque itération. Cela améliorera votre point de vue. : P
underscore_d
@underscore_d constpeut certainement aider à obtenir de meilleures performances, non pas à cause de la magie du constmot-clé lui-même, mais parce que le compilateur peut activer certaines optimisations s'il sait que les données ne seront pas modifiées, ce qui ne serait pas possible autrement. Regardez ce extrait d'une conférence de Jason Turner pour un exemple en direct de cela.
brainplot
@brainplot Je n'ai pas dit que ce n'était pas possible. J'ai dit que ce n'était pas son principal avantage et que je pense qu'il est surestimé, alors que le véritable avantage est un code sémantiquement correct et sûr.
underscore_d
@underscore_d Oui, je suis d'accord là-dessus. Je voulais juste rendre explicite ce qui constpeut (presque indirectement) conduire à des avantages de performance; juste au cas où quelqu'un lisant ceci pourrait penser "Je ne prendrai pas la peine d'ajouter constsi le code généré n'est jamais affecté de quelque manière que ce soit", ce qui n'est pas vrai.
brainplot
0

c'est simple, cbegin renvoie un itérateur constant où begin renvoie juste un itérateur

pour une meilleure compréhension, prenons deux scénarios ici

Scénario 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;
}

cela fonctionnera car ici l'itérateur i n'est pas constant et peut être incrémenté de 5

maintenant, utilisons cbegin et cend en les désignant comme scénario d'itérateurs constants - 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;
}

cela ne fonctionnera pas, car vous ne pouvez pas mettre à jour la valeur en utilisant cbegin et cend qui retourne l'itérateur constant

PAVAN KUMAR
la source