pass - std::function c++
¿Debería pasar una función std:: por const-reference? (3)
Como es habitual en C ++ 11, pasar por valor / referencia / referencia constante depende de lo que haga con su argumento. std::function
no es diferente.
Pasar por valor le permite mover el argumento a una variable (generalmente una variable miembro de una clase):
struct Foo {
Foo(Object o) : m_o(std::move(o)) {}
Object m_o;
};
Cuando sepa que su función moverá su argumento, esta es la mejor solución, de esta manera sus usuarios pueden controlar cómo llaman a su función:
Foo f1{Object()}; // move the temporary, followed by a move in the constructor
Foo f2{some_object}; // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor
Creo que ya sabes la semántica de (no) const-referencias, así que no voy a profundizar en el tema. Si necesita que agregue más explicaciones sobre esto, solo pregunte y lo actualizaré.
Digamos que tengo una función que toma una std::function
void callFunction(std::function<void()> x)
{
x();
}
¿Debo pasar x
por const-reference en su lugar ?:
void callFunction(const std::function<void()>& x)
{
x();
}
¿La respuesta a esta pregunta cambia según lo que hace la función con ella? Por ejemplo, si se trata de una función o constructor miembro de la clase que almacena o inicializa la std::function
en una variable miembro.
Si desea rendimiento, pase por valor si lo está almacenando.
Supongamos que tiene una función llamada "ejecutar esto en el hilo de UI".
std::future<void> run_in_ui_thread( std::function<void()> )
que ejecuta algún código en el hilo "ui", luego señala el future
cuando termina. (Es útil en marcos de interfaz de usuario donde el hilo de la interfaz de usuario es donde se supone que debe meterse con los elementos de la interfaz de usuario)
Tenemos dos firmas que estamos considerando:
std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)
Ahora, es probable que usemos estos de la siguiente manera:
run_in_ui_thread( [=]{
// code goes here
} ).wait();
que creará un cierre anónimo (un lambda), construirá una std::function
partir de él, lo pasará a la función run_in_ui_thread
y luego esperará a que termine de ejecutarse en el hilo principal.
En el caso (A), la std::function
se construye directamente a partir de nuestra lambda, que luego se usa dentro de run_in_ui_thread
. El lambda se move
d a la std::function
, por lo que cualquier estado móvil se transporta de manera eficiente en él.
En el segundo caso, se crea una std::function
temporal, la lambda se move
d a ella, luego esa std::function
temporal se usa por referencia dentro de run_in_ui_thread
.
Hasta ahora, muy bien, los dos se desempeñan de manera idéntica. ¡Excepto que run_in_ui_thread
va a hacer una copia de su argumento de función para enviar al hilo de la interfaz de usuario para que se ejecute! (volverá antes de que termine con él, por lo que no puede usar una referencia). Para el caso (A), simplemente move
la std::function
en su almacenamiento a largo plazo. En el caso (B), estamos obligados a copiar la std::function
.
Esa tienda hace pasar por valor más óptimo. Si hay alguna posibilidad de que esté almacenando una copia de la std::function
, pase por valor. De lo contrario, cualquier forma es más o menos equivalente: la única desventaja de by-value es si está tomando la misma std::function
voluminosa y tiene un submétodo después de otro uso. Salvo eso, un move
será tan eficiente como un const&
.
Ahora, hay otras diferencias entre los dos que se activan principalmente si tenemos un estado persistente dentro de la std::function
.
Supongamos que la std::function
almacena algún objeto con un operator() const
, pero también tiene algunos miembros de datos mutable
que modifica (¡qué grosero!).
En std::function<> const&
case, los miembros de datos mutable
modificados se propagarán fuera de la llamada de función. En el caso std::function<>
, no lo harán.
Este es un caso de esquina relativamente extraño.
Desea tratar la std::function
como lo haría con cualquier otro tipo de peso pesado y móvil. Moverse es barato, copiar puede ser costoso.
Si le preocupa el rendimiento y no está definiendo una función de miembro virtual, lo más probable es que no use std::function
.
Hacer que el tipo de functor sea un parámetro de plantilla permite una mayor optimización que std::function
, incluida la delimitación de la lógica del functor. Es probable que el efecto de estas optimizaciones supere en gran medida las preocupaciones de copia contra indirección sobre cómo pasar la std::function
.
Más rápido:
template<typename Functor>
void callFunction(Functor&& x)
{
x();
}