c++ - sirve - declaración if-evaluación de cortocircuito vs legibilidad
if else c++ para que sirve (10)
1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario para "optimizarlo" y aún así proporcionar SCE?
No creo que se permita tal optimización;
especialmente
OtherComplicatedFunctionCall()
podría tener algunos efectos secundarios.
2) ¿Cuál es la mejor práctica en tal situación? ¿Es la única posibilidad (cuando quiero SCE) tener todo lo que necesito directamente dentro y "formatearlo para que sea lo más legible posible"?
Prefiero refactorizarlo en una función o una variable con un nombre descriptivo; que preservará tanto la evaluación de corto circuito como la legibilidad
bool getSomeResult() {
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
}
...
if (getSomeResult())
{
//do stuff
}
Y a medida que implementamos
getSomeResult()
basado en
SomeComplicatedFunctionCall()
y
OtherComplicatedFunctionCall()
, podríamos descomponerlos de forma recursiva si aún son complicados.
A veces, una declaración
if
puede ser bastante complicada o larga, por lo que, en aras de la legibilidad, es mejor extraer llamadas complicadas antes que
if
.
por ejemplo esto:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
dentro de esto
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(el ejemplo proporcionado no es tan malo, es solo para ilustración ... imagine otras llamadas con múltiples argumentos, etc.)
Pero con esta extracción perdí la evaluación de cortocircuito (SCE).
- ¿Realmente pierdo SCE cada vez? ¿Hay algún escenario en el que el compilador pueda "optimizarlo" y aún así proporcionar SCE?
- ¿Hay formas de mantener la legibilidad mejorada del segundo fragmento sin perder SCE?
1) ¿Realmente pierdo SCE cada vez? ¿Se permite al compilador algún escenario para "optimizarlo" y aún así proporcionar SCE?
No, no lo haces, pero se aplica de manera diferente:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
Aquí, el compilador ni siquiera ejecutará
OtherComplicatedFunctionCall()
si
SomeComplicatedFunctionCall()
devuelve true.
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
Aquí, ambas funciones
se
ejecutarán porque deben almacenarse en
b1
y
b2
.
Ff
b1 == true
entonces
b2
no será evaluado (SCE).
Pero
OtherComplicatedFunctionCall()
se ha ejecutado.
Si
b2
se usa en ninguna otra parte, el compilador
podría
ser lo suficientemente inteligente como para alinear la llamada a la función dentro de if si la función no tiene efectos secundarios observables.
2) ¿Cuál es la mejor práctica en tal situación? ¿Es la única posibilidad (cuando quiero SCE) tener todo lo que necesito directamente dentro y "formatearlo para que sea lo más legible posible"?
Eso depende.
¿
Necesita
OtherComplicatedFunctionCall()
para ejecutarse debido a los efectos secundarios o el impacto de rendimiento de la función es mínimo, entonces debe usar el segundo enfoque para facilitar la lectura.
De lo contrario, manténgase en SCE a través del primer enfoque.
1) Sí, ya no tienes SCE. De lo contrario, tendrías eso
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
funciona de una forma u otra dependiendo de si hay una declaración
if
más adelante.
Demasiado complejo.
2) Esto se basa en la opinión, pero para expresiones razonablemente complejas puede hacer:
if (SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()) {
Si es demasiado complejo, la solución obvia es crear una función que evalúe la expresión y la llame.
La legibilidad es necesaria si trabaja en una empresa y su código será leído por otra persona. Si escribe un programa para usted, depende de usted si desea sacrificar el rendimiento por un código comprensible.
Muy extraño: estás hablando de legibilidad cuando nadie menciona el uso de comentarios dentro del código:
if (somecomplicated_function() || // let me explain what this function does
someother_function()) // this function does something else
...
Además de eso, siempre precedí mis funciones con algunos comentarios, sobre la función en sí, sobre su entrada y salida, y a veces pongo un ejemplo, como puede ver aquí:
/*---------------------------*/
/*! interpolates between values
* @param[in] X_axis : contains X-values
* @param[in] Y_axis : contains Y-values
* @param[in] value : X-value, input to the interpolation process
* @return[out] : the interpolated value
* @example : interpolate([2,0],[3,2],2.4) -> 0.8
*/
int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)
Obviamente, el formato a utilizar para sus comentarios puede depender de su entorno de desarrollo (Visual Studio, JavaDoc bajo Eclipse, ...)
En lo que respecta a SCE, supongo que con esto te refieres a lo siguiente:
bool b1;
b1 = somecomplicated_function(); // let me explain what this function does
bool b2 = false;
if (!b1) { // SCE : if first function call is already true,
// no need to spend resources executing second function.
b2 = someother_function(); // this function does something else
}
if (b1 || b2) {
...
}
Otra posibilidad que cortocircuita y tiene las condiciones en un solo lugar:
bool (* conditions [])()= {&a, &b, ...}; // list of conditions
bool conditionsHold = true;
for(int i= 0; i < sizeOf(conditions); i ++){
if (!conditions[i]()){;
conditionsHold = false;
break;
}
}
//conditionsHold is true if all conditions were met, otherwise false
Podría poner el bucle en una función y dejar que la función acepte una lista de condiciones y generar un valor booleano.
Si tiene largas cadenas de condiciones y qué mantener algunos de los cortocircuitos, entonces podría usar variables temporales para combinar múltiples condiciones. Tomando su ejemplo, sería posible hacer, por ejemplo,
bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
if (b && some_other_expression) { ... }
Si tiene un compilador compatible con C ++ 11, puede usar expresiones lambda para combinar expresiones en funciones, similares a las anteriores:
auto e = []()
{
return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall();
};
if (e() && some_other_expression) { ... }
También puedes usar:
bool b = someComplicatedStuff();
b = b || otherComplicatedStuff(); // it has to be: b = b || ...; b |= ...; is bitwise OR and SCE is not working then
y SCE funcionará.
Pero no es mucho más legible que, por ejemplo:
if (
someComplicatedStuff()
||
otherComplicatedStuff()
)
Tiendo a descomponer las condiciones en varias líneas, es decir:
if( SomeComplicatedFunctionCall()
|| OtherComplicatedFunctionCall()
) {
Incluso cuando se trata de múltiples operadores (&&) solo necesita avanzar la sangría con cada par de paréntesis. SCE todavía entra en acción, no es necesario usar variables. Escribir código de esta manera me hizo mucho más legible durante años. Ejemplo más complejo:
if( one()
||( two()> 1337
&&( three()== ''foo''
|| four()
)
)
|| five()!= 3.1415
) {
Una solución natural se vería así:
bool b1 = SomeCondition();
bool b2 = b1 || SomeOtherCondition();
bool b3 = b2 || SomeThirdCondition();
// any other condition
bool bn = bn_1 || SomeFinalCondition();
if (bn)
{
// do stuff
}
Esto tiene los beneficios de ser fácil de entender, ser aplicable a todos los casos y tener un comportamiento de corto circuito.
Esta fue mi solución inicial: un buen patrón en llamadas a métodos y cuerpos for-loop es el siguiente:
if (!SomeComplicatedFunctionCall())
return; // or continue
if (!SomeOtherComplicatedFunctionCall())
return; // or continue
// do stuff
Uno obtiene los mismos beneficios de rendimiento de la evaluación de cortocircuito, pero el código parece más legible.