Digamos que tengo una función que toma un std::function
:
void callFunction(std::function<void()> x)
{
x();
}
¿Debería pasar x
por const-reference en su lugar ?:
void callFunction(const std::function<void()>& x)
{
x();
}
¿La respuesta a esta pregunta cambia dependiendo de lo que la función haga con ella? Por ejemplo, si se trata de una función miembro de clase o un constructor que almacena o inicializa std::function
en una variable miembro.
sizeof(std::function)
no ser más que2 * sizeof(size_t)
, que es el tamaño mínimo que alguna vez consideraría para una referencia constante.std::function
contenedor sea tan importante como la complejidad de copiarlo. Si se trata de copias profundas, podría ser mucho más costoso de lo quesizeof
sugiere.move
la función en?operator()()
esconst
así que una referencia constante debería funcionar. Pero nunca he usado std :: function.Respuestas:
Si desea rendimiento, pase por valor si lo está almacenando.
Supongamos que tiene una función llamada "ejecutar esto en el hilo de la interfaz de usuario".
que ejecuta un código en el subproceso "ui", luego señala el
future
cuando está hecho. (Útil en los marcos de la interfaz de usuario donde el hilo de la interfaz de usuario es donde se supone que debes jugar con los elementos de la interfaz de usuario)Tenemos dos firmas que estamos considerando:
Ahora, es probable que usemos estos de la siguiente manera:
que creará un cierre anónimo (una lambda), construirá un
std::function
fuera de él, lo pasará a larun_in_ui_thread
función y luego esperará a que termine de ejecutarse en el hilo principal.En el caso (A),
std::function
se construye directamente a partir de nuestra lambda, que luego se utiliza dentro derun_in_ui_thread
. La lambda semove
introduce en elstd::function
, por lo que cualquier estado móvil se transporta de manera eficiente.En el segundo caso,
std::function
se crea un temporal , semove
introduce el lambda en él, luego ese temporalstd::function
se usa como referencia dentro delrun_in_ui_thread
.Hasta ahora, todo bien: los dos se desempeñan de manera idéntica. ¡Excepto
run_in_ui_thread
que va a hacer una copia de su argumento de función para enviar al hilo ui para ejecutar! (regresará antes de terminar con él, por lo que no puede simplemente usar una referencia a él). Para el caso (A), simplementemove
elstd::function
en su almacenamiento a largo plazo. En el caso (B), nos vemos obligados a copiar elstd::function
.Esa tienda hace que pasar por valor sea más óptimo. Si existe alguna posibilidad de que esté almacenando una copia del
std::function
pase por valor. De lo contrario, de cualquier manera es más o menos equivalente: el único inconveniente delstd::function
sub -valor es si está tomando el mismo voluminoso y tiene un método secundario tras otro. Salvo eso, amove
será tan eficiente como aconst&
.Ahora, hay algunas otras diferencias entre los dos que se aplican principalmente si tenemos un estado persistente dentro del
std::function
.Suponga que
std::function
almacena algún objeto con aoperator() const
, pero también tiene algunosmutable
miembros de datos que modifica (¡qué grosero!).En el
std::function<> const&
caso, losmutable
miembros de datos modificados se propagarán fuera de la llamada a la función. En elstd::function<>
caso, no lo harán.Este es un caso de esquina relativamente extraño.
Desea tratar
std::function
como lo haría con cualquier otro tipo posiblemente pesado y de bajo costo. Mudarse es barato, copiar puede ser costoso.fuente
const&
, solo veo el costo de la operación de copia.std::function
se crea a partir de la lambda. En (A), lo temporal se elide en el argumento derun_in_ui_thread
. En (B) se pasa una referencia a dicho temporalrun_in_ui_thread
. Mientras susstd::function
s se creen a partir de lambdas como temporales, esa cláusula se mantiene. El párrafo anterior trata del caso dondestd::function
persiste. Si no estamos almacenando, solo creamos desde una lambdafunction const&
yfunction
tenemos exactamente la misma sobrecarga.run_in_ui_thread()
. ¿Hay solo una firma que diga "Pasar por referencia, pero no guardaré la dirección"?std::future<void> run_in_ui_thread( std::function<void()>&& )
Si le preocupa el rendimiento y no está definiendo una función miembro virtual, lo más probable es que no deba usarlo
std::function
en absoluto.Hacer que el functor escriba un parámetro de plantilla permite una mayor optimización que
std::function
, incluida la integración de la lógica del functor. Es probable que el efecto de estas optimizaciones supere en gran medida las preocupaciones de copiar frente a indirección sobre cómo pasarstd::function
.Más rápido:
fuente
std::forward<Functor>(x)();
, para preservar la categoría de valor del functor, ya que es una referencia "universal". Sin embargo, no va a hacer una diferencia en el 99% de los casos.callFunction(std::move(myFunctor));
std::move
si ya no lo necesitará de otra manera, o pasarlo directamente si no desea salir del objeto existente. Las reglas de colapso de referencia aseguran quecallFunction<T&>()
tenga un parámetro de tipoT&
, noT&&
.Como es habitual en C ++ 11, pasar por valor / referencia / referencia constante depende de lo que haga con su argumento.
std::function
no es diferentePasar por valor le permite mover el argumento a una variable (generalmente una variable miembro de una clase):
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:
Creo que ya conoce la semántica de las (no) referencias constantes, por lo que no voy a pensar en ello. Si necesita que agregue más explicaciones sobre esto, solo pregunte y lo actualizaré.
fuente