Bucle basado en el rango de C ++ 11: obtenga el elemento por valor o referencia a const

215

Al leer algunos ejemplos de bucles basados ​​en rango, sugieren dos formas principales 1 , 2 , 3 , 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

o

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Bien.

Cuando no necesitamos cambiar vecelementos, OMI, los ejemplos sugieren usar una segunda versión (por valor). Por qué no sugieren algo que consthaga referencia (al menos no he encontrado ninguna sugerencia directa):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

¿No es mejor? ¿No evita una copia redundante en cada iteración mientras es un const?

masoud
fuente

Respuestas:

390

Si no desea cambiar los elementos y evitar hacer copias, entonces esta auto const &es la opción correcta:

for (auto const &x : vec)

Quien te sugiera que uses auto &está equivocado. Ingnóralos.

Aquí está el resumen:

  • Elija auto xcuándo quiere trabajar con copias.
  • Elija auto &xcuándo desea trabajar con elementos originales y puede modificarlos.
  • Elija auto const &xcuándo desea trabajar con elementos originales y no los modificará.
Nawaz
fuente
21
Gracias por la gran respuesta. Supongo que también debería señalarse que const auto &xes equivalente a su tercera opción.
smg
77
@mloskot: es equivalente. (¿y qué quiere decir con "pero es la misma sintaxis" ? La sintaxis es notablemente diferente.)
Nawaz
14
Falta: auto&&cuando no desea hacer una copia innecesaria, y no le importa si la modifica o no, y simplemente quiere trabajar.
Yakk - Adam Nevraumont
44
¿Se aplica la distinción de referencia a las copias si solo está trabajando con tipos fundamentales como int / double?
44
@racarate: no puedo comentar sobre la velocidad general , y creo que nadie puede, sin perfilarlo primero. Francamente, no basaré mi elección en la velocidad, sino en la claridad del código. Si quiero inmutabilidad, lo usaría constseguro. Sin embargo, si sería auto const &, o auto const tiene poca diferencia. Elegiría auto const &ser más consistente.
Nawaz
23

Si tiene un std::vector<int>o std::vector<double>, entonces está bien usarlo auto(con copia de valor) en lugar de const auto&, ya que copiar un into un doublees barato:

for (auto x : vec)
    ....

Pero si tiene un std::vector<MyClass>, donde MyClasstiene alguna semántica de copia no trivial (por ejemplo std::string, alguna clase personalizada compleja, etc.), entonces sugiero usar const auto&para evitar copias profundas :

for (const auto & x : vec)
    ....
Mr.C64
fuente
3

Cuando no necesitamos cambiar vecelementos, los ejemplos sugieren utilizar la primera versión.

Luego dan una sugerencia equivocada.

Por qué no sugieren algo que const las referencias

Porque dan una sugerencia incorrecta :-) Lo que mencionas es correcto. Si solo desea observar un objeto, no es necesario crear una copia, y no es necesario tener una no constreferencia a él.

EDITAR:

Veo que todas las referencias que enlaza proporcionan ejemplos de iteración sobre un rango de intvalores o algún otro tipo de datos fundamental. En ese caso, dado que copiar un intno es costoso, crear una copia es básicamente equivalente a (si no es más eficiente que) tener una observación const &.

Sin embargo, este no es el caso en general para los tipos definidos por el usuario. Copiar UDT puede ser costoso, y si no tiene una razón para crear una copia (como modificar el objeto recuperado sin alterar el original), entonces es preferible usar a const &.

Andy Prowl
fuente
1

Voy a ser contrario aquí y decir que no hay necesidad auto const &en un rango basado en bucle. Dime si crees que la siguiente función es tonta (no en su propósito, sino en la forma en que está escrita):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Aquí, el autor ha creado una referencia constante vpara usar en todas las operaciones que no modifican v. Esto es una tontería, en mi opinión, y se puede hacer el mismo argumento para usar auto const &como la variable en un rango basado en bucle en lugar de solo auto &.

Benjamin Lindley
fuente
3
@BenjaminLindley: ¿Entonces puedo inferir que también argumentarías const_iteratoren un bucle for? ¿Cómo se asegura de no cambiar los elementos originales de un contenedor cuando itera sobre él?
Nawaz
2
@BenjaminLindley: ¿Por qué su código no se compilaría sin él const_iterator? Déjame adivinar, el contenedor sobre el que iteras es const. ¿Pero por qué es constpara empezar? ¿En algún lugar lo estás usando constpara asegurar qué?
Nawaz
8
La ventaja de hacer objetos constes como las ventajas de usar private/ protecteden las clases. Evita más errores.
masoud
2
@BenjaminLindley: Oh, pasé por alto eso. Entonces, en este caso, la implementación también es tonta. Pero si esa función no se modifica v, la implementación estaría bien IMO. Y si el ciclo nunca modifica los objetos a través de los cuales itera, me parece correcto tener una referencia const. Veo su punto, sin embargo. El mío es que el bucle representa una unidad de código muy similar a la función, y dentro de esa unidad no hay instrucciones que necesiten modificar el valor. Por lo tanto, puede "etiquetar" toda la unidad como const, como lo haría con una constfunción miembro.
Andy Prowl
1
Entonces, ¿crees que const &para el rango basado es tonto, porque escribiste un ejemplo imaginario irrelevante de un programador imaginario que hizo algo tonto con la calificación cv ? ... OK entonces
underscore_d