2011-09-23 20 views
13

Esta es probablemente una cuestión filosófica, pero me encontré con el siguiente problema:¿Por qué las instancias std :: function tienen un constructor predeterminado?

Si se define un std :: función, y no inicializa correctamente, la aplicación va a chocar, como esto:

typedef std::function<void(void)> MyFunctionType; 
MyFunctionType myFunction; 
myFunction(); 

Si la función se pasa como argumento, como esto:

void DoSomething (MyFunctionType myFunction) 
    { 
    myFunction(); 
    } 

Luego, por supuesto, también se bloquea. Esto quiere decir que me veo obligado a añadir el código de comprobación de la siguiente manera:

void DoSomething (MyFunctionType myFunction) 
    { 
    if (!myFunction) return; 
    myFunction(); 
    } 

que requieren estas comprobaciones me da un flash-back a los viejos tiempos C, en las que también tuvo que comprobar todos los argumentos de puntero de forma explícita:

void DoSomething (Car *car, Person *person) 
    { 
    if (!car) return;  // In real applications, this would be an assert of course 
    if (!person) return; // In real applications, this would be an assert of course 
    ... 
    } 

Afortunadamente, podemos utilizar referencias en C++, lo que me impide escribir estas comprobaciones (suponiendo que la persona que llama no pasó el contenido de un nullptr a la función:

void DoSomething (Car &car, Person &person) 
    { 
    // I can assume that car and person are valid 
    } 

Así, WH y do std :: function instances tiene un constructor predeterminado? Sin el constructor predeterminado, no tendría que agregar comprobaciones, al igual que para otros argumentos normales de una función. Y en los casos 'raros' en los que desea pasar una función 'opcional' std ::, aún puede pasarle un puntero (o usar boost :: opcional).

+4

No se cuelga; arroja una excepción. –

+0

Lea acerca de 'functors': http://www.sgi.com/tech/stl/functors.html –

+0

" Me veo obligado a agregar un código de comprobación ": es una pena que las personas que llaman no puedan reparar su código. Parece extraño abortar para ahorrarles la necesidad de manejar la excepción. Aún así, podría ser peor, si tomaras un puntero de función y no se molestaran en inicializarlo, entonces tendría un valor indeterminado y el comportamiento sería indefinido. Probablemente estén cometiendo un error mucho peor llamando '' strlen'' que llamando a su función. –

Respuesta

14

Es cierto, pero esto también es cierto para otros tipos. P.ej. si quiero que mi clase tenga una Persona opcional, entonces hago de mi miembro de datos un Persona-puntero. ¿Por qué no hacer lo mismo para std :: functions? ¿Qué tiene de especial la función std :: que pueda tener un estado 'inválido'?

No tiene un estado "no válido". No es más válido que esto:

std::vector<int> aVector; 
aVector[0] = 5; 

Lo que tienes es un vacío function, al igual que aVector es un vacío vector. El objeto está en un estado muy bien definido: el estado de no tener datos.

Ahora, vamos a considerar el "puntero a funcionar" sugerencia:

void CallbackRegistrar(..., std::function<void()> *pFunc); 

¿Cómo hay que llamar a eso? Bueno, aquí hay una cosa que no puede hacer:

void CallbackFunc(); 
CallbackRegistrar(..., CallbackFunc); 

eso no es permitido porque CallbackFunc es una función, mientras que el tipo de parámetro es un std::function<void()>*. Esos dos no son convertibles, por lo que el compilador se quejará. Así que con el fin de hacer la llamada, lo que tiene que hacer esto:

void CallbackFunc(); 
CallbackRegistrar(..., new std::function<void()>(CallbackFunc)); 

Acaba de new introducido en el cuadro. Usted ha asignado un recurso; ¿Quién será responsable de eso? CallbackRegistrar? Obviamente, es posible que desee utilizar algún tipo de puntero inteligente, por lo que el desorden de la interfaz aún más con:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc); 

Eso es un montón de molestia activos y de costra, sólo para pasar una función alrededor. La forma más sencilla de evitar esto es permitir que std::function sea vacío. Al igual que permitimos que std::vector esté vacío. Al igual que permitimos que std::string esté vacío. Al igual que permitimos que std::shared_ptr esté vacío. Y así.

En pocas palabras: std::functioncontiene una función. Es un titular para un tipo invocable. Por lo tanto, existe la posibilidad de que no contenga ningún tipo invocable.

+2

Obviamente, querría 'boost :: optional MSalters

+3

@MSalters: Sí, eso funcionaría. Pero lamentablemente 'opcional 'no es parte de la biblioteca estándar de C++. Y para esas almas pobres y desafortunadas a las que no se les permite usar Boost, simplemente no es una opción. –

+0

Vota, buena redacción: opcional ... simplemente no es una opción, jaja! –

5

Uno de los casos de uso más comunes para std::function es registrar devoluciones de llamadas, para llamarlas cuando se cumplan ciertas condiciones. Permitir instancias no inicializadas permite registrar las devoluciones de llamada solo cuando es necesario, de lo contrario se vería forzado a pasar siempre al menos algún tipo de función no operativa.

+0

Es cierto, pero esto también es cierto para otros tipos. P.ej. si quiero que mi clase tenga una Persona opcional, entonces hago de mi miembro de datos un Persona-puntero. ¿Por qué no hacer lo mismo para std :: functions? ¿Qué tiene de especial la función std :: que pueda tener un estado 'inválido'? – Patrick

+4

En mi opinión, debería ver std :: function como un tipo de puntero inteligente para callables. No esperarías usar un puntero a un shared_ptr, ¿o sí? –

1

Hay casos donde no se puede inicializar todo en la construcción (por ejemplo, cuando un parámetro depende del efecto en otra construcción que a su vez depende del efecto en la primera ...).

En estos casos, tiene que romper necesariamente el ciclo, admitiendo un estado inválido identificable para ser corregido más tarde. Así que construyes el primero como "nulo", construyes el segundo elemento y reasignas el primero.

Puede, en realidad, evitar los controles, si -donde se usa una función- usted concede que dentro del constructor del objeto que lo integra, siempre regresará después de una reasignación válida.

+0

No creo que haya ninguna forma de "corregir más tarde" la función std :: vacía. Si está vacío en la creación, siempre está vacío. (Por supuesto, si lo considera un estado inválido o no, es una cuestión diferente.) – max

+0

@max parece ser un problema del "mismo concepto - diferente redacción", para mí. –

+0

Supongo que no entendí tu argumento entonces. Está diciendo que puede usar una función 'std :: vacía' cuando no sabe qué valor quiere para ella hasta más tarde (es decir, no puede inicializarla en el controlador). Estoy de acuerdo en que sería un buen caso de uso. Sin embargo, lo que no veo es lo que harás más tarde, cuando finalmente * hagas * el valor que deseas. No es como si pudieras cambiar una 'función std :: vacía' en otra cosa? – max

5

La respuesta es probablemente histórica: std::function es un reemplazo de los punteros a las funciones, y los punteros a las funciones tienen la capacidad de ser NULL. Por lo tanto, cuando desee ofrecer una compatibilidad sencilla con los punteros de función, debe ofrecer un estado no válido.

El estado inválido identificable no es realmente necesario ya que, como mencionó, boost::optional hace bien ese trabajo. Así que diría que std::function están ahí por el bien de la historia.

9

En realidad, su aplicación no debería fallar.

§ 20.8.11.1 Clase bad_function_call [func.wrap.Badcall]

1/ una excepción de tipo bad_function_call es lanzada por function::operator() (20.8.11.2.4) cuando el objeto función de contenedor no tiene objetivo.

El comportamiento está perfectamente especificado.

+2

Puede especificarse, pero la especificación también dice que el programa termina (es decir, se bloquea) en el caso de una excepción que deja 'main'. Que es lo que sucede si no lo atrapas. –

+2

@NicolBolas: Tiene razón, pero en realidad no considero esto una falla, ya que muchas operaciones podrían generar una excepción: el simple hecho de asignar algo a 'std :: function' podría realizar una asignación de memoria. Por lo tanto, no manejar excepciones no está relacionado con el hecho de que 'std :: function' podría no inicializarse con una devolución de llamada. –

0

De la misma manera que puede agregar un nullstate a un tipo de functor que no tiene uno, puede envolver un functor con una clase que no admita un nullstate. El primero requiere agregar estado, el último no requiere un estado nuevo (solo una restricción). Por lo tanto, aunque no conozco la razón de ser del diseño std::function, es compatible con el uso promedio & más sencillo, sin importar lo que desee.

Saludos & HTH.,

0

Usted sólo tiene que utilizar std :: función para devoluciones de llamada, se puede utilizar una sencilla función de plantilla de ayuda que envía sus argumentos al controlador si no está vacío:

template <typename Callback, typename... Ts> 
void SendNotification(const Callback & callback, Ts&&... vs) 
{ 
    if (callback) 
    { 
     callback(std::forward<Ts>(vs)...); 
    } 
} 

y utilizarlo de la siguiente manera:

std::function<void(int, double>> myHandler; 
... 
SendNotification(myHandler, 42, 3.15); 
Cuestiones relacionadas