c++ - usando - ¿Evitando la instrucción if dentro de un ciclo for?
sentencia for (3)
En la función, estoy haciendo la comprobación if (índice) en cada ronda de mi for-loop, aunque el resultado siempre es el mismo. Esto va en contra de "preocuparse por el rendimiento".
Si este es realmente el caso, el predictor de bifurcación no tendrá problemas para predecir el resultado (constante). Como tal, esto solo causará una sobrecarga leve por errores de predicción en las primeras iteraciones. No hay nada de qué preocuparse en términos de rendimiento
En este caso, abogo por mantener la prueba dentro del ciclo para mayor claridad.
Tengo una clase llamada Writer
que tiene una función writeVector
así:
void Drawer::writeVector(vector<T> vec, bool index=true)
{
for (unsigned int i = 0; i < vec.size(); i++) {
if (index) {
cout << i << "/t";
}
cout << vec[i] << "/n";
}
}
Intento no tener un código duplicado, mientras sigo preocupándome por el rendimiento. En la función, estoy haciendo la comprobación if (index)
en cada ronda de mi for
-loop, aunque el resultado siempre es el mismo. Esto va en contra de "preocuparse por el rendimiento".
Podría evitarlo fácilmente colocando el cheque fuera de mi for
-loop. Sin embargo, obtendré un montón de código duplicado:
void Drawer::writeVector(...)
{
if (index) {
for (...) {
cout << i << "/t" << vec[i] << "/n";
}
}
else {
for (...) {
cout << vec[i] << "/n";
}
}
}
Entonces estas son las dos soluciones "malas" para mí. Lo que he estado pensando, son dos funciones privadas, una de ellas saca el índice y luego llama al otro. El otro solo supera el valor. Sin embargo, no sé cómo usarlo con mi programa, aún necesitaría la verificación if
para ver a cuál llamar ...
De acuerdo con el problema, el polimorfismo parece una solución correcta. Pero no puedo ver cómo debería usarlo aquí. ¿Cuál sería la forma preferida de resolver este tipo de problema?
Este no es un programa real, solo me interesa aprender cómo se debe resolver este tipo de problema.
Para expandir la respuesta de Ali, que es perfectamente correcta pero aún duplica algún código (parte del cuerpo del bucle, desafortunadamente no se puede evitar cuando se usa el patrón de estrategia) ...
Concedido en este caso particular, la duplicación de código no es mucho, pero hay una manera de reducirlo aún más, lo que es útil si el cuerpo de la función es más grande que unas pocas instrucciones .
La clave es usar la capacidad del compilador para realizar la eliminación constante de plegado / código muerto . Podemos hacerlo mapeando manualmente el valor de tiempo de ejecución del index
a un valor en tiempo de compilación (fácil de hacer cuando solo hay un número limitado de casos, dos en este caso) y utilizamos un argumento de plantilla sin tipo que se conoce en tiempo de compilación:
template<bool index = true>
// ^^^^^^ note: the default value is now part of the template version
// see below to understand why
void writeVector(const vector<int>& vec) {
for (size_t i = 0; i < vec.size(); ++i) {
if (index) { // compile-time constant: this test will always be eliminated
cout << i << "/t"; // this will only be kept if "index" is true
}
cout << vec[i] << "/n";
}
}
void writeVector(const vector<int>& vec, bool index)
// ^^^^^ note: no more default value, otherwise
// it would clash with the template overload
{
if (index) // runtime decision
writeVector<true>(vec);
// ^^^^ map it to a compile-time constant
else
writeVector<false>(vec);
}
De esta forma terminamos con un código compilado que es equivalente a su segundo ejemplo de código (externo if
/ interno for
) pero sin duplicar el código nosotros mismos. Ahora podemos hacer que la versión de plantilla de writeVector
tan complicada como queramos, siempre habrá una sola pieza de código para mantener.
Observe cómo la versión de plantilla (que toma una constante de tiempo de compilación en forma de un argumento de plantilla sin tipo) y la versión sin plantilla (que toma una variable de tiempo de ejecución como argumento de función) están sobrecargadas. Esto le permite elegir la versión más relevante según sus necesidades, teniendo una sintaxis bastante similar y fácil de recordar en ambos casos:
writeVector<true>(vec); // you already know at compile-time which version you want
// no need to go through the non-template runtime dispatching
writeVector(vec, index); // you don''t know at compile-time what "index" will be
// so you have to use the non-template runtime dispatching
writeVector(vec); // you can even use your previous syntax using a default argument
// it will call the template overload directly
Pase en el cuerpo del lazo como un funtor. Se inserta en tiempo de compilación, sin penalización de rendimiento.
La idea de transmitir lo que varía es omnipresente en la Biblioteca estándar de C ++. Se llama el patrón de estrategia.
Si puede usar C ++ 11, puede hacer algo como esto:
#include <iostream>
#include <set>
#include <vector>
template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {
for (const auto& e : c)
f(index++, e);
}
int main() {
using namespace std;
set<char> s{''b'', ''a'', ''c''};
// indices starting at 1 instead of 0
for_each_indexed(s, [](size_t i, char e) { cout<<i<<''/t''<<e<<''/n''; }, 1u);
cout << "-----" << endl;
vector<int> v{77, 88, 99};
// without index
for_each_indexed(v, [](size_t , int e) { cout<<e<<''/n''; });
}
Este código no es perfecto pero entiendes la idea.
En el viejo C ++ 98 se ve así:
#include <iostream>
#include <vector>
using namespace std;
struct with_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << i << ''/t'' << e << ''/n'';
}
};
struct without_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << e << ''/n'';
}
};
template <typename Func>
void writeVector(const vector<int>& v, Func f) {
for (vector<int>::size_type i=0; i<v.size(); ++i) {
f(cout, i, v[i]);
}
}
int main() {
vector<int> v;
v.push_back(77);
v.push_back(88);
v.push_back(99);
writeVector(v, with_index());
cout << "-----" << endl;
writeVector(v, without_index());
return 0;
}
Una vez más, el código está lejos de ser perfecto, pero te da la idea.