Inicializar variables en una declaración "si"

80

Leí que en C ++ 17 podemos inicializar variables en ifdeclaraciones como esta

if (int length = 2; length == 2)
    //execute something

En vez de

int length = 2;
if (length == 2)
    //do something

Aunque es más corto, afecta la legibilidad del código (especialmente para las personas que no conocen esta nueva característica), lo que supongo que es una mala práctica de codificación para el desarrollo de software grande.

¿Existe alguna ventaja de utilizar esta función además de acortar el código?

Arne
fuente
38
¿Además del alcance?
DeiDei
10
Supongo que alguien ha dicho hace unos años "Leí que en C ++ 11, podemos crear declaraciones lambda como esta (...) Aunque es más corta, afecta la legibilidad del código (especialmente para las personas que no saben esta nueva característica), que supongo que es una mala práctica de codificación para el desarrollo de software de gran tamaño ".
R2RT
9
Yo diría que tiene exactamente la misma longitud, no más corta.
user7860670
5
opinión pura, por lo tanto, no es una respuesta: if (int length = 2; length == 2)es quizás sorprendente que lo veas la primera vez, pero no es nada complejo que uno no pueda entender, así que ya la segunda vez ya no será una gran sorpresa y declarar cosas en el ámbito al que pertenece es uno de los principales factores que contribuyen a la legibilidad. En mi humilde opinión que su premisa es equivocada;)
largest_prime_is_463035818
14
Preocuparse por la legibilidad del código para las personas que no conocen el idioma en el que está escrito (que es lo que significa "no conozco esta nueva característica") es una carrera hacia el fondo.

Respuestas:

97

Limita el alcance de lengthal ifsolo. De modo que obtiene los mismos beneficios que obtuvimos originalmente cuando se nos permitió escribir

for(int i = 0; i < ... ; ++i) {
   // ...
}

En lugar de la variable goteando

int i;
for(i = 0; i < ... ; ++i) {
   // ...
}

Las variables de corta duración son mejores por varias razones. Pero para nombrar un par:

  1. Cuanto más tiempo dure algo, menos cosas debe tener en cuenta al leer líneas de código no relacionadas. Si ino existe fuera del ciclo o ifdeclaración, entonces no necesitamos preocuparnos por su valor fuera de ellos. Tampoco debemos preocuparnos de que su valor interactúe con otras partes del programa que están fuera de su alcance previsto (lo que puede suceder si lo ianterior se reutiliza en otro ciclo). Hace que el código sea más fácil de seguir y razonar.

  2. Si la variable contiene un recurso, ese recurso ahora se mantiene durante el período más breve posible. Y esto sin llaves extrañas. También se deja en claro que el recurso está relacionado ifsolo. Considere esto como un ejemplo motivador

    if(std::lock_guard _(mtx); guarded_thing.is_ready()) {
    }
    

Si sus colegas no conocen la función, enséñeles. Apaciguar a los programadores que no desean aprender es una mala excusa para evitar funciones.

StoryTeller - Unslander Monica
fuente
12
Voy a agarrar esta última frase y pegarla en un cartel de dos metros.
Quentin
3
Además, las variables de corta duración (de ámbito estricto) deberían reducir los errores porque no puede reutilizar accidentalmente una variable más adelante en el código una vez que se cumpla su propósito.
Galik
1
O con un puntero débil:if (auto p = ptr.lock(); p && p->foo()) bar(*p);
Deduplicador
1
3. El compilador puede reutilizar el espacio de la pila en más casos. (Si alguna vez pasa i por referencia o puntero a una función externa, por ejemplo.)
TLW
La mejor pregunta podría ser "¿cuál es la ventaja de esto? {int i = 2; if (i == 2) {...}}" (Tenga en cuenta el alcance adicional).
TLW
24

¿Existe alguna ventaja de utilizar esta función además de acortar el código?

Reduces el alcance variable. Esto tiene sentido y aumenta la legibilidad, ya que fortalece la localidad de identificadores sobre los que necesita razonar. Estoy de acuerdo en que ifdeben evitarse las declaraciones de inicio largas dentro de las declaraciones, pero para las cosas cortas, está bien.

Tenga en cuenta que ya puede realizar la inicialización y la ramificación en el resultado en pre-C ++ 17:

int *get(); // returns nullptr under some condition

if (int *ptr = get())
    doStuff();

Esto está sujeto a la opinión personal de cada uno, pero puede considerar una condición explícita más legible:

if (int *ptr = get(); ptr != nullptr)
    doStuff();

Además, argumentar en contra de la legibilidad de una característica refiriéndose al hecho de que la gente no está acostumbrada a ella es peligroso. La gente no estaba acostumbrada a los consejos inteligentes en algún momento, sin embargo, todos estamos de acuerdo hoy (supongo) en que es bueno que estén allí.

lubgr
fuente
4
podría usar if (auto p =get ())ya que el operador bool está definido
sudo rm -rf slash
19

La nueva forma de la instrucción if tiene muchos usos.

Actualmente, el inicializador se declara antes de la declaración y se filtra al ámbito ambiental, o se utiliza un ámbito explícito. Con la nueva forma, dicho código se puede escribir de manera más compacta, y el control de alcance mejorado hace que algunas construcciones que antes eran propensas a errores sean un poco más robustas.

Propuesta estándar abierta para declaración If con inicializador

ingrese la descripción de la imagen aquí

Entonces, en resumen, esta declaración simplifica los patrones de código comunes y ayuda a los usuarios a mantener los alcances ajustados.

¡Espero que ayude!

Abhishek Sinha
fuente
¿Podría aclarar que está citando parte de la propuesta? Especialmente el segundo párrafo. Sugiero citas en bloque.
StoryTeller - Unslander Monica
Gracias @StoryTeller, Sí, cité el segundo párrafo de la propuesta de open-std que se incorporó en C ++ 17.
Abhishek Sinha
10

Con el fin de minimizar el alcance de las variables, existe un idioma que define un recurso solo si es válido en el momento de la creación (por ejemplo , objetos de flujo de archivos ):

if(auto file = std::ifstream("filename"))
{
    // use file here
}
else
{
    // complain about errors here
}

// The identifier `file` does not pollute the wider scope

A veces, desea poder revertir la lógica de esa prueba para hacer que la falla sea la cláusula principal y el recurso válido la elsecláusula. Anteriormente, esto no era posible. Pero ahora podemos hacer:

if(auto file = std::ifstream("filename"); !file)
{
    // complain about errors here
}
else
{
    // use file here
}

Un ejemplo podría lanzar una excepción:

if(auto file = std::ifstream(filename); !file)
    throw std::runtime_error(std::strerror(errno));
else
{
    // use file here
}

A algunas personas les gusta codificar para que una función se interrumpa al principio de un error y progrese. Este modismo pone la lógica del aborto físicamente por encima de la lógica de continuación que algunas personas pueden encontrar más natural.

Galik
fuente
8

Es especialmente útil para eventos lógicos. Considere este ejemplo:

char op = '-';
if (op != '-' && op != '+' && op != '*' && op != '/') {
    std::cerr << "bad stuff\n";
}

Parece un poco duro. A menos que esté muy familiarizado con las OR, ANDnegaciones, es posible que deba hacer una pausa y pensar en esta lógica, que generalmente es un diseño deficiente. Con el if-initializationpuedes sumar expresividad.

char op = '-';
if (bool op_valid = (op == '-') || (op == '+') || (op == '*') || (op == '/'); !op_valid) {
    std::cerr << "bad stuff\n";
} 

la variable nombrada también se puede reutilizar dentro del if. P.ej:

if (double distance = std::sqrt(a * a + b * b); distance < 0.5){
    std::cerr << distance << " is too small\n";
}

Esto es genial, especialmente dado que la variable tiene alcance y, por lo tanto, no contamina el espacio después.

Pila de Danny
fuente
2
Me doy cuenta de que es subjetivo, pero prefiero su versión "aproximada" a la que tiene el inicializador if. Me resulta mucho más fácil de leer y comprender.
Fabio dice Reincorporar a Monica
@FabioTurati Supongo que es porque estás muy familiarizado con él y la otra versión es nueva. Pero con el tiempo espero que el if-initializer supere a cualquier cosa similar.
Stack Danny
7

Esta es una extensión de una función existente, que ayuda a la legibilidad en mi experiencia.

if (auto* ptr = get_something()) {
}

Aquí ambos creamos la variable ptry probamos que no sea nula. El alcance de ptrse limita a donde es válido. Es mucho, mucho más fácil convencerse a sí mismo de que todo uso de ptres válido.

Pero, ¿qué pasa si estamos hablando de algo que no se convierte de boolesa manera?

if (auto itr = find(bob)) {
}

Eso no funciona. Pero con esta nueva función podemos:

if (auto itr = find(bob); itr != end()) {
}

Agregue una cláusula que diga "cuándo es válida esta inicialización".

En esencia, esto nos da un conjunto de tokens que significan "inicializar alguna expresión, y cuando sea válida, hacer algún código. Cuando no sea válida, deséchela".

Ha sido idiomático hacer el truco de la prueba de puntero desde C ++ 98. Una vez que hayas aceptado eso, esta extensión es natural.

Yakk - Adam Nevraumont
fuente