2010-01-12 28 views
21

acabo de leer el código de std :: for_each:¿Por qué std :: for_each (de, a, función) devuelve la función?

template<class InputIterator, class Function> 
Function for_each(InputIterator first, InputIterator last, Function f) 
{ 
    for (; first!=last; ++first) f(*first); 
    return f; 
} 

y no podía ver ningún buenas razones para esta función de plantilla para devolver la función de entrada. ¿Alguien tiene algún ejemplo sobre dónde esto sería útil?

Respuesta

40

Es para permitirle acumular estado en su función y luego devolverlo a su código de llamada. Por ejemplo, su función (como una clase functor) podría tener un int int para contar el número de veces que se ha llamado.

Aquí hay una página con algunos ejemplos: http://xenon.arcticus.com/c-morsels-std-for-each-functors-member-variables

+0

Por supuesto, ¿por qué no pensé en esto? Thx :) – larsmoa

+0

Me dijeron hace mucho tiempo aquí (por Charles Bailey, creo), que este comportamiento no está garantizado. No le creí; Pensé que el estándar, aunque no era claro, tenía más sentido en ese contexto. (¿Por qué otra cosa podríamos obtener una función?) Pero solo una advertencia, tal vez esto es solo un comportamiento de compilador "estándar" y no un comportamiento estándar. – GManNickG

+4

@GMan: la norma exige el retorno de la función (§25.1.1/2). –

1

es útil si desea guardar (y luego usar) el estado del functor entre llamadas, por ejemplo, contar el número de elementos en una colección o indicar algún error al procesar un elemento estableciendo alguna variable interna.

-3

No hay ninguna razón particular, supongo. Sin embargo, lo que puede hacer es usar la función devuelta en otra llamada foreach, evitando así escribir el nombre de la función dos veces y posiblemente cometiendo un error allí.

1

Si pasa un objeto de función, también conocido como functor, y tiene estado, devolver el objeto de función le permite acceder a su estado después de iterar la secuencia. Supongamos que tiene un objeto de función que calcula tres variables diferentes de la secuencia y las mantiene en variables miembro. Cada vez que se llama al funtor, actualiza los contadores. Si for_each no devolviera el objeto, ¿cómo obtendría el resultado?

Nota ... esta es la razón por la que siempre debe implementar la construcción de copias y la asignación de objetos de funciones con estado.

+3

NO TIENE QUE IMPLEMENTAR el constructor de copia o el operador de asignación para un functor, al igual que para cualquier otra clase; es muy probable que los valores predeterminados hagan lo que sea necesario. –

2

Devolver la función básicamente hace std::for_each en una imitación mediocre de std::accumulate. Te permite acumular algo en la función/functor, y luego recuperar ese valor acumulado cuando está hecho. Casi en cualquier momento que piense que esto podría ser una medida útil, probablemente debería considerar usar std::accumulate.

+0

en realidad std :: accumulate es muy torpe. – CashCow

7

Puede que Alex Stepanov tuviera el paradigma de programación funcional, pero encontrará que tanto std::accumulate como pasan sus operandos (la función y el valor acumulado) por valor, en lugar de por referencia. Por lo tanto:

class MyFunctor 
{ 
    Y val; 
    public: 
    MyFunctor() : val() {} 

    void operator()(X const& x) 
    { 
     // do something to modify val based on x 
    } 

    Y getValue() const { return val; } 
}; 

Ahora bien, si se intenta:

MyFunctor f; 
for_each(coll.begin(), coll.end(), f); 
Y y = f.getValue(); 

No funcionará porque for_each se ha ocupado de copias de f. Por supuesto, podría tener una instancia de shared_ptr<Y> internamente que, por lo tanto, apuntaría a la misma instancia. También puede hacer que val dentro de MyFunctor sea una referencia, crearlo fuera del ciclo y pasarlo a MyFunctor.

Sin embargo, el idioma que le acaba de hacer:

Y y = for_each(coll.begin(), coll.end(), MyFunctor()).getValue(); 

agradable y conveniente, todo en una línea.

a hacer lo mismo con std::accumulate se haría así:

class MyFunctor2 
{ 
public: 
     Y operator()(Y y, X const& x) const 
     { 
     // create a new Y based on the old one and x 
     ... 
     } 
}; 

Y y = std::accumulate(coll.begin(), coll.end(), Y(), MyFunctor2()); 

se puede utilizar una función (o en C++ 11 una lambda) en lugar de un funtor. Tenga en cuenta que el funtor aquí no tiene estado, y pasa su objeto inicializado como parámetro, que puede ser temporal.

Ahora sabemos que Y se puede copiar. std::accumulate usa by value en Y, no una modificación in situ. Incidentalmente, cuando en el lugar modificar realmente es más eficiente, hay una solución sin necesidad de escribir un nuevo algoritmo (por ejemplo accumulate2 que utiliza + = o modificación de referencia) mediante el uso de una firma de la función de:

Y * func(Y* py, X const &); // function modifies *py in-place then returns py 

luego llamar:

Y y; 
std::accumulate(coll.begin(), coll.end(), &y, func); 

"Sabemos" que el valor de retorno será & y. Podemos hacer uso de esto si queremos acceder a un miembro de Y en un solo lugar, p.

Y y; 
Z z = std::accumulate(coll.begin(), coll.end(), &y, func)->getZ(); 

Por cierto, una diferencia clave para la copia en for_each y la copia en accumulate es la complejidad/número de copias que va a hacer. Con for_each habrá como máximo 2 copias hechas de su functor: una como parámetro en la función y otra en la devolución. Digo "como máximo" porque la optimización del valor de retorno podría reducir la segunda de estas copias. Con accumulate, copia con cada elemento de la colección, es decir, O(N) en lugar de tiempo constante. Entonces, si la copia es levemente costosa, la copia doble en el funtor no será un gasto importante iterando un pequeño número de veces sobre colecciones grandes, mientras que para acumular sería (y la sugerencia sería el corte del puntero).

Cuestiones relacionadas