c++ - funciones - ¿Debo copiar una función std:: o siempre puedo tomar una referencia a ella?
std::cout (5)
¿Puedo almacenar la función como referencia ya que std :: function es solo un puntero a la función y se garantiza que el "código ejecutable" de la función permanecerá en la memoria?
std::function
no es solo un puntero a la función. Es un contenedor alrededor de un objeto invocable arbitrario y administra la memoria utilizada para almacenar ese objeto. Al igual que con cualquier otro tipo, es seguro almacenar una referencia solo si tiene alguna otra forma de garantizar que el objeto referido siga siendo válido siempre que se use esa referencia.
A menos que tenga una buena razón para almacenar una referencia y una forma de garantizar que siga siendo válida, guárdela por valor.
Pasar por referencia de referencia al constructor es seguro, y probablemente más eficiente que pasar un valor. Pasar por referencia no const
es una mala idea, ya que le impide pasar un temporal, por lo que el usuario no puede pasar directamente un lambda, el resultado de un bind
, o cualquier otro objeto invocable excepto std::function<int(int)>
sí mismo.
En mi aplicación C ++ (usando Visual Studio 2010), necesito almacenar una función std ::, como esta:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction &myFunction);
private:
MyFunction m_myFunction; // Should I use this one?
MyFunction &m_myFunction; // Or should I use this one?
};
Como puede ver, agregué el argumento de la función como referencia en el constructor.
Pero, ¿cuál es la mejor manera de almacenar la función en mi clase?
- ¿Puedo almacenar la función como referencia ya que std :: function es solo un puntero a la función y se garantiza que el "código ejecutable" de la función permanecerá en la memoria?
- ¿Debo hacer una copia en caso de que se pase una lambda y regrese la persona que llama?
Mi instinto me dice que es seguro almacenar una referencia (incluso una referencia constante). Espero que el compilador genere código para el lambda en tiempo de compilación, y mantenga este código ejecutable en memoria ''virtual'' mientras la aplicación se está ejecutando. Por lo tanto, el código ejecutable nunca se ''elimina'' y puedo almacenar una referencia de forma segura. Pero, ¿es esto realmente cierto?
Como regla general (especialmente si los usa para algún sistema con muchos subprocesos), pase por valor. Realmente no hay forma de verificar desde un hilo que el objeto subyacente todavía está alrededor con un tipo de referencia, por lo que te abres a una raza muy repugnante y a errores de bloqueo.
Otra consideración es cualquier variable de estado oculta en la función std ::, para la cual es muy poco probable que la modificación sea segura para subprocesos. Esto significa que incluso si la llamada a la función subyacente es segura para subprocesos, la llamada "()" del reiniciador std :: function a su alrededor PUEDE NO SER. Puede recuperar el comportamiento deseado siempre utilizando copias locales de hilo de la función std :: porque cada una de ellas tendrá una copia aislada de las variables de estado.
Copie todo lo que quiera Es copiable La mayoría de los algoritmos en la biblioteca estándar requieren que los funtores sí lo estén.
Sin embargo, pasar por referencia probablemente sea más rápido en casos no triviales, por lo que sugiero pasar por referencia constante y almacenar por valor para que no tenga que preocuparse por la administración del ciclo de vida. Asi que:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (const Myfunction &myFunction);
// ^^^^^ pass by CONSTANT reference.
private:
MyFunction m_myFunction; // Always store by value
};
Al pasar por una referencia constante o de valor razonable, le promete a la persona que llama que no modificará la función mientras aún pueda llamarla. Esto evita que se modifique la función por error y, por lo general, se debe evitar hacerlo intencionalmente, ya que es menos legible que usar el valor de retorno.
Editar: Originalmente dije "CONSTANT o rvalue" arriba, pero el comentario de Dave me hizo buscarlo y, de hecho, la referencia de validación no acepta valores.
Si pasa la función al constructor por referencia, y no hace una copia, no tendrá suerte cuando la función quede fuera del alcance fuera de este objeto, ya que la referencia ya no será válida. Eso ya se ha dicho en las respuestas anteriores.
Lo que quería agregar es que, en cambio, podría pasar la función por valor , no referencia, al constructor. ¿Por qué? bueno, necesitas una copia de todos modos, así que si pasas por alto el compilador puede optimizar la necesidad de hacer una copia cuando se pasa un temporal (como una expresión lambda escrita en el lugar).
Por supuesto, haga lo que haga, posiblemente haga otra copia cuando asigne la función transferida a la variable, así que use std::move
para eliminar esa copia. Ejemplo:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
MyClass (Myfunction myFunction): m_myfunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
Por lo tanto, si pasan un valor de referencia al anterior, el compilador optimiza la primera copia en el constructor y std :: move elimina el segundo :)
Si su (único) constructor toma una referencia constante, tendrá que hacer una copia de la misma en la función, independientemente de cómo se transfiere.
La alternativa es definir dos constructores, para tratar con lvalues y rvalues por separado:
class MyClass
{
public:
typedef std::function<int(int)> MyFunction;
//takes lvalue and copy constructs to local var:
MyClass (const Myfunction & myFunction): m_myfunction(myFunction)
{}
//takes rvalue and move constructs local var:
MyClass (MyFunction && myFunction): m_myFunction(std::move(myFunction))
{}
private:
MyFunction m_myFunction;
};
Ahora, aplica manualmente valores diferentes y elimina la necesidad de copiar en ese caso al manejarlo explícitamente (en lugar de dejar que el compilador lo maneje por usted). Puede ser marginalmente más eficiente que el primero pero también es más código.
La referencia (probablemente vista un poco por aquí) relevante (y una muy buena lectura): cpp-next.com/archive/2009/08/want-speed-pass-by-value
Te sugiero que hagas una copia:
MyFunction m_myFunction; //prefferd and safe!
Es seguro porque si el objeto original sale del alcance destruyéndose a sí mismo, la copia todavía existirá en la instancia de la clase.