¿Cuál es la razón detrás de cbegin / cend?

Respuestas:

228

Es bastante simple. Digamos que tengo un vector:

std::vector<int> vec;

Lo lleno con algunos datos. Entonces quiero obtener algunos iteradores. Quizás pasarlos por ahí. Quizás para std::for_each:

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

En C ++ 03, SomeFunctorera libre de poder modificar el parámetro que obtiene. Claro, SomeFunctorpodría tomar su parámetro por valor o por const&, pero no hay forma de asegurarse de que lo haga. No sin hacer algo tonto como este:

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

Ahora, presentamos cbegin/cend:

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

Ahora, tenemos garantías sintácticas que SomeFunctorno pueden modificar los elementos del vector (sin un const-cast, por supuesto). Obtenemos explícitamente const_iterators, y por SomeFunctor::operator()lo tanto se llamará con const int &. Si toma sus parámetros como int &, C ++ emitirá un error de compilación.


C ++ 17 tiene una solución más elegante a este problema: std::as_const. Bueno, al menos es elegante cuando se usa basado en rango for:

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

Esto simplemente devuelve const&a al objeto que se proporciona.

Nicol Bolas
fuente
1
Pensé que el nuevo protocolo era cbegin (vec) en lugar de vec.cbegin ().
Kaz Dragon
20
@Kaz: No hay std::cbegin/cendfunciones libres como std::begin/std::endexisten. Fue un descuido por parte del comité. Si esas funciones existieran, esa sería generalmente la forma de usarlas.
Nicol Bolas
20
Aparentemente, std::cbegin/cendse agregará en C ++ 14. Ver en.cppreference.com/w/cpp/iterator/begin
Adi Shavit el
9
@NicolBolas es for(auto &item : std::as_const(vec))equivalente a for(const auto &item : vec)?
luizfls
9
@luizfls Sí. Su código dice que el artículo no se modificará colocando el consten la referencia. Nicol ve el contenedor como constante, por lo que autodeduce una constreferencia. La OMI auto const& itemes más fácil y más clara. No está claro por qué std::as_const()es bueno aquí; Puedo ver que sería útil cuando se pasa algo que no constes código genérico donde no podemos controlar el tipo que se usa, pero con rango for, podemos hacerlo, por lo que me parece un ruido agregado allí.
underscore_d
66

Más allá de lo que dijo Nicol Bolas en su respuesta , considere la nueva autopalabra clave:

auto iterator = container.begin();

Con auto, no hay forma de asegurarse de que begin()devuelva un operador constante para una referencia de contenedor no constante. Entonces ahora lo haces:

auto const_iterator = container.cbegin();
Stefan Majewsky
fuente
2
@allyourcode: no ayuda. Para el compilador, const_iteratores solo otro identificador. Ninguna de las versiones utiliza una búsqueda de los miembros habituales typedefs decltype(container)::iteratoro decltype(container)::const_iterator.
Aschepler
2
@aschepler No entiendo tu segunda oración, pero creo que te perdiste el "const" frente a "auto" en mi pregunta. Cualquiera que sea el auto, parece que const_iterator debería ser const.
allyourcode el
26
@allyourcode: Eso le daría un iterador que es constante, pero eso es muy diferente de un iterador a datos constantes.
Aschepler
2
Hay una manera simple de asegurarse de obtener un const_iteratorcon auto: Escriba una plantilla de función auxiliar llamada make_constpara calificar el argumento del objeto.
Columbo
17
Tal vez ya no estoy en la mentalidad de C ++, pero no puedo ver una conexión entre los conceptos de "una manera simple" y "escribir una plantilla de función auxiliar". ;)
Stefan Majewsky
15

Tome esto como un caso práctico

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

La asignación falla porque ites un iterador no constante. Si usó cbegin inicialmente, el iterador habría tenido el tipo correcto.

Johannes Schaub - litb
fuente
8

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

para que un programador pueda obtener directamente un const_iterator incluso de un contenedor no const

Dieron este ejemplo

vector<MyType> v;

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

Sin embargo, cuando el recorrido de un contenedor es solo para inspección, es una práctica generalmente preferida usar un const_iterator para permitir que el compilador diagnostique violaciones de corrección de const

Tenga en cuenta que el documento de trabajo también menciona plantillas de adaptadores, que ahora se han finalizado como std::begin()y std::end()y que también funcionan con matrices nativas. Los correspondientes std::cbegin()y std::cend()curiosamente faltan a partir de este momento, pero también podrían agregarse.

TemplateRex
fuente
5

Me topé con esta pregunta ... Sé que ya está respondida y es solo un nodo lateral ...

auto const it = container.begin() es un tipo diferente entonces auto it = container.cbegin()

la diferencia para int[5](usando el puntero, que sé que no tiene el método de inicio, pero muestra muy bien la diferencia ... pero funcionaría en c ++ 14 para std::cbegin()y std::cend(), que es esencialmente lo que uno debería usar cuando está aquí) ...

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.
fuente
2

iteratory const_iteratortienen una relación de herencia y se produce una conversión implícita cuando se compara o se asigna al otro 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()y cend()aumentará el rendimiento en este caso.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
fuente
Me tomó un tiempo darme cuenta de que querías decir que el rendimiento se guarda al evitar la conversión al inicializar y comparar iteradores, no el mito popular de que constel principal beneficio es el rendimiento (que no lo es: es un código semánticamente correcto y seguro). Pero, si bien tiene un punto, (A) autohace que no sea un problema; (B) al hablar sobre el rendimiento, se perdió una cosa principal que debería haber hecho aquí: almacenar en caché el enditerador declarando una copia de él en la condición init del forbucle, y compararlo, en lugar de obtener una nueva copia valor para cada iteración. Eso mejorará tu punto. : P
underscore_d
@underscore_d constdefinitivamente puede ayudar a lograr un mejor rendimiento, no debido a cierta magia en la constpalabra clave en sí, sino porque el compilador puede habilitar algunas optimizaciones si sabe que los datos no se modificarán, lo que de otra manera no sería posible. Mira este fragmento de la charla de Jason Turner para ver un ejemplo en vivo de esto.
brainplot
@brainplot No dije que no podía. Dije que ese no es su principal beneficio y que creo que se exagera cuando el beneficio real es un código semánticamente correcto y seguro.
underscore_d
@underscore_d Sí, estoy de acuerdo con eso. Solo estaba haciendo explícito que constpuede (casi indirectamente) conducir a beneficios de rendimiento; por si alguien que lee esto puede pensar "No me molestaré en agregar constsi el código generado no se ve afectado de ninguna manera", lo cual no es cierto.
brainplot
0

es simple, cbegin devuelve un iterador constante donde begin solo devuelve un iterador

para una mejor comprensión, tomemos dos escenarios aquí

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

esto se ejecutará porque aquí el iterador i no es constante y puede incrementarse en 5

ahora usemos cbegin y cend denotándolos como escenario 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;
}

esto no va a funcionar, porque no puede actualizar el valor usando cbegin y cend, que devuelve el iterador constante

PAVAN KUMAR
fuente