¿Debería pasar una función std :: por const-reference?

Digamos que tengo una función que toma una std::function

 void callFunction(std::function x) { x(); } 

¿Debo pasar x por const-reference en su lugar ?:

 void callFunction(const std::function& 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 run_in_ui_thread( std::function ) 

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 run_in_ui_thread( std::function ) // (A) std::future run_in_ui_thread( std::function 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 void callFunction(Functor&& x) { x(); } 

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é.