2010-05-18 16 views
5

Empecé a usar contenedores STL porque me resultaron muy útiles cuando necesitaba la funcionalidad de una lista, un conjunto y un mapa, y no tenía nada más disponible en mi entorno de programación. No me importaron mucho las ideas detrás de esto. La documentación de STL era interesante hasta el punto de que se trataba de funciones, etc. Luego me salté la lectura y simplemente usé los contenedores.¿Cómo se usan las funciones de stl como for_each?

Pero ayer, todavía relajado por mis vacaciones, lo probé y quería ir un poco más de camino. Así que utilicé la función de transformación (¿puedo aplaudir un poco, gracias?).

Desde un punto de vista académico realmente se veía interesante y funcionó. Pero lo que me molesta es que si intensificas el uso de esas funciones, necesitas miles de clases de ayuda para la mayoría de las cosas que quieres hacer en tu código. Toda la lógica del programa está dividida en pedacitos. Este corte no es el resultado de buenos hábitos de codificación; es solo una necesidad técnica. Algo, eso hace que mi vida probablemente sea más difícil, no más fácil.

Aprendí de la manera difícil, que siempre debe elegir el enfoque más simple que resuelve el problema en cuestión. No puedo ver qué hace, por ejemplo, la función for_each que justifica el uso de una clase auxiliar sobre varias líneas simples de código que se encuentran dentro de un ciclo normal para que todos puedan ver lo que está sucediendo.

Me gustaría saber, ¿qué piensas sobre mis preocupaciones? ¿Lo viste como yo cuando comenzaste a trabajar de esta manera y cambiaste de opinión cuando te acostumbraste? ¿Hay beneficios que pasé por alto? O simplemente ignoras estas cosas como yo lo hice (y seguiré haciéndolo, probablemente).

Gracias.

PD: Sé que hay un verdadero for_each loop en boost. Pero lo ignoro aquí ya que es una manera conveniente para mis bucles habituales con iteradores, supongo.

+3

'std :: mem_fun' y ayuda similar, pero esta es una de las cosas dolorosas sobre C++ IMO. C++ 0x lambdas ayudará con esto. Se pone aún peor cuando intentas usar cosas como 'std :: bind1st' y composición. – msandiford

+2

No creo que BOOST_FOR_EACH sea solo de conveniencia. Es más conciso y más expresivo que tu ciclo habitual. Es una mejora real. Creo que Lambdas hará que usar el STL sea mucho más fácil y lo llevará al nivel al que pertenece. – pmr

Respuesta

0

Estas son realmente preocupaciones reales, y estas se están abordando en la próxima versión del estándar C++ ("C++ 0x") que se publicará a finales de este año o en 2011. Esa versión de C++ introduce una noción llamada C++ lambdas que le permite a uno construir funciones anónimas simples dentro de otra función, lo que hace que sea muy fácil lograr lo que quiere sin romper su código en pequeños pedazos. Lambdas (¿experimentalmente?) Es compatible con GCC a partir de GCC 4.5.

+0

También en Visual Studio 2010. – Puppy

1

Supongo que la comunidad de C++ tiene las mismas preocupaciones. El nuevo estándar C++ 0x validado presenta lambdas. Esta nueva función le permitirá usar el algoritmo al escribir funciones auxiliares simples directamente en la lista de parámetros del algoritmo.

std::transform(in.begin(), int.end(), out.begin(), [](int a) { return ++a; }) 
3

me resulta más útil cuando se utiliza junto con boost::bind y boost::lambda de modo que yo no tengo que escribir mi propia funtor. Esto es sólo un pequeño ejemplo:

class A 
{ 
public: 
    A() : m_n(0) 
    { 
    } 

    void set(int n) 
    { 
     m_n = n; 
    } 

private: 
    int m_n; 
}; 

int main(){  

    using namespace boost::lambda; 

    std::vector<A> a; 
    a.push_back(A()); 
    a.push_back(A()); 

    std::for_each(a.begin(), a.end(), bind(&A::set, _1, 5)); 


    return 0; 
} 
7

Toda la lógica del programa es en rodajas en trozos pequeños. Este corte no es el resultado de buenos hábitos de codificación. Es solo una necesidad técnica. Algo, eso hace que mi vida probablemente sea más difícil, no más fácil.

Tienes razón, hasta cierto punto.Es por eso que la próxima revisión al estándar de C++ se sumará expresiones lambda, que le permite hacer algo como esto:

std::for_each(vec.begin(), vec.end(), [&](int& val){val++;}) 

pero también creo que a menudo es un hábito de codificación buena para dividir el código como se requiere actualmente. De hecho, está separando el código que describe la operación que desea hacer, desde el acto de aplicarlo a una secuencia de valores. Es un código extra repetitivo, y algunas veces es molesto, pero creo que a menudo también conduce a un código bueno y limpio.

hacer lo anterior hoy se vería así:

int incr(int& val) { return val+1} 

// and at the call-site 
std::for_each(vec.begin(), vec.end(), incr); 

En lugar de distensión hasta el sitio de llamada con un bucle completo, tenemos una sola línea que describe:

  • la que se realiza la operación (si es el apropiado nombre)
  • la que los elementos se ven afectados

por lo que es más corto y transmite la misma información que el ciclo, pero de forma más concisa. Creo que esas son cosas buenas. El inconveniente es que tenemos que definir la función incr en otro lugar. Y a veces eso no vale la pena el esfuerzo, y es por eso que se están agregando lambdas al lenguaje.

+2

Creo que en el 60% de los casos el functor contendrá no más de 3 líneas de código y probablemente no se reutilizará en otro lugar (ningún cuerpo buscará estos para reutilizarlos porque los nombres de esos funtores se están desordenando realmente rápido). En otro 30%, el código que se transfiere al funtor no es trivial, pero es único en la base de código sin necesidad de refactorizarlo en un objeto. Según lo veo, hay un pequeño número de ocasiones (<10%) en las que tiene beneficios reales en cuanto a la calidad del código al producir funtores. Pero estoy trabajando en una aplicación comercial ... – user331471

+0

Estoy de acuerdo con thomas-gies. Normalmente defino mis funtores en el método que los usa, ya que no se usan en ningún otro lado. –

+1

falta un punto y coma después de devolver val + 1;) –

3

Encontrará desacuerdo entre los expertos, pero diría que for_each y transform son un poco una distracción. El poder de STL está en separar algoritmos no triviales de los datos que se operan.

La biblioteca lambda de Boost definitivamente vale la pena experimentar para ver cómo te va con ella. Sin embargo, incluso si la sintaxis es satisfactoria, la asombrosa cantidad de maquinaria involucrada tiene desventajas en términos de tiempo de compilación y capacidad de depuración.

Mi consejo es usar:

for (Range::const_iterator i = r.begin(), end = r.end(); i != end(); ++i) 
{ 
    *out++ = .. // for transform 
} 

en lugar de for_each y transform, pero lo más importante familiarizarse con los algoritmos que son muy útil: sort, unique, rotate elegir tres al azar.

0

Esas bibliotecas como STL y Boost son complejos también porque se necesitan para resolver todas las necesidades y trabajar en cualquier Plateform.

Como usuario de estas bibliotecas, ¿no está planeando una nueva versión de .NET? - puedes usar sus golosinas simplificadas.

Aquí es posiblemente un simple foreach de Boost me gusta usar:

BOOST_FOREACH(string& item in my_list) 
{ 
    ... 
} 

se ve mucho más limpio y más sencillo de utilizar.begin(), .end(), etc y sin embargo funciona para casi cualquier iterables colección (no solo arrays/vectores).

1

Las clases locales son una gran característica para resolver esto.Por ejemplo:

void IncreaseVector(std::vector<int>& v) 
{ 
class Increment 
{ 
public: 
    int operator()(int& i) 
    { 
    return ++i; 
    } 
}; 

std::for_each(v.begin(), v.end(), Increment()); 
} 

OMI, esto es demasiada complejidad para apenas un incremento, y va a ser más claro que escribirlo en forma de una llanura regulares de bucle. Pero cuando la operación que desea realizar en una secuencia se vuelve más compleja. Entonces me parece útil separar claramente la operación que se realizará sobre cada elemento de la oración real del ciclo. Si el nombre de su functor se elige correctamente, el código obtiene un plus descriptivo.

+0

Esto ayuda a resolver el "¿dónde debería poner el funtor?" pregunta. Pero si la clase Incremento necesita información sobre el contexto, aún necesita declarar miembros y un constructor, etc. ¿O también se puede evitar con este enfoque? En ruby, los bloques pueden acceder a la pila del contexto. – user331471

+0

@ thomas-gies: hay algunas restricciones en este enfoque con respecto al acceso al contexto. Eche un vistazo a esto para obtener más información: http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=191. –

+0

¿Has compilado esto? En C++ 98, las clases locales no se pueden usar como argumentos de plantilla. En C++ 0x pueden, pero allí tenemos lambdas. –

2

Incrementar un contador para cada elemento de una secuencia no es un buen ejemplo para for_each.

Si observa ejemplos mejores, puede encontrar que hace que el código sea mucho más claro de comprender y usar.

Esto es un poco de código que he escrito hoy:

// assume some SinkFactory class is defined 
// and mapItr is an iterator of a std::map<int,std::vector<SinkFactory*> > 

std::for_each(mapItr->second.begin(), mapItr->second.end(), 
    checked_delete<SinkFactory>); 

checked_delete forma parte del impulso, pero la implementación es trivial y tiene el siguiente aspecto:

template<typename T> 
void checked_delete(T* pointer) 
{ 
    delete pointer; 
} 

La alternativa hubiera sido escribir este :

for(vector<SinkFactory>::iterator pSinkFactory = mapItr->second.begin(); 
    pSinkFactory != mapItr->second.end(); ++pSinkFactory) 
    delete (*pSinkFactory); 

Más que eso, una vez que tenga que checked_delete escrito una vez (o si ya usa boost), puede eliminar punteros en cualquier secuencia aywhere, con el mismo código, sin importar qué tipos está iterando (es decir, no tiene que declarar vector<SinkFactory>::iterator pSinkFactory).

También hay una pequeña mejora en el rendimiento del hecho de que con la for_each container.end() se llama solamente una vez, y potencialmente grandes mejoras en el rendimiento en función de la aplicación for_each (que podría ser implementado de manera diferente dependiendo de la etiqueta de repetidor recibido).

Además, si combina boost :: bind con los algoritmos de secuencia stl puede hacer todo tipo de cosas divertidas (consulte aquí: http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html#with_algorithms).

Cuestiones relacionadas