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).
Por supuesto, ¿por qué no pensé en esto? Thx :) – larsmoa
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
@GMan: la norma exige el retorno de la función (§25.1.1/2). –