Guardia de llamadas de funciones
Pregunta
Supongamos que tengo una función gratuita llamada InitFoo
. Me gustaría proteger esta función de ser llamado varias veces por accidente. Sin pensarlo, escribí lo siguiente:
void InitFoo()
{
{
static bool flag = false;
if(flag) return;
flag = true;
}
//Actual code goes here.
}
Sin embargo, esto parece una gran verruga. InitFoo
lo hace no Necesita preservar cualquier otra información de estado. ¿Alguien puede sugerir una forma de lograr el mismo objetivo sin la fealdad?
Las macros no cuentan, por supuesto.
Solución
Puedes hacerlo con un poco de fealdad diferente:
struct InitFoo
{
InitFoo()
{
// one-time code goes here
}
};
void Foo()
{
static InitFoo i;
}
Sigues usando static
, pero ahora no necesitas hacer tu propia verificación de bandera - static
ya se pone en una bandera y una comprobación, por lo que solo se construye i
una vez.
Otros consejos
Bueno, un constructor solo se llama automáticamente una vez. Si crea una sola instancia de esta clase:
class Foo
{
public:
Foo(void)
{
// do stuff
}
}
Después //do stuff
solo se ejecutará una vez. La única forma de ejecutarlo dos veces es crear otra instancia de la clase.
Puede evitar esto usando un único. En efecto, //do stuff
Posiblemente solo se puede llamar una vez.
Me gustaría proteger esta función de ser llamado varias veces por accidente.
Para mí, esto suena como un problema que solo surgirá durante la depuración. Si ese es el caso, simplemente haría lo siguiente:
void InitFoo()
{
#ifndef NDEBUG
static bool onlyCalledOnce = TRUE;
assert(onlyCalledOnce);
onlyCalledOnce = FALSE;
#endif
...
}
El propósito de esta verruga en particular se discerne fácilmente simplemente mirándola, y causará una falla de afirmación agradable, grande y llamativa si un programador comete el error de llamar InitFoo
mas de una vez. También desaparecerá por completo en el código de producción. (cuando NDEBUG
se define).
editar: Una nota rápida sobre la motivación:
Llamar a una función init más de una vez es probablemente un gran error. Si el usuario final de esta función lo ha llamado por error dos veces, ignorar en silencio ese error probablemente no sea el camino a seguir. Si no vas al assert()
ruta, recomendaría al menos descargar un mensaje a stdout
o stderr
.
Así es exactamente como lo haría. Podrías usar algún puntero de funciones barajando si quieres una alternativa:
static void InitFoo_impl()
{
// Do stuff.
// Next time InitFoo is called, call abort() instead.
InitFoo = &abort;
}
void (*InitFoo)() = &InitFoo_impl;
¿También necesita que sea seguro de múltiples hilos? Mire en el patrón Singleton con bloqueo de doble verificación (lo cual es sorprendente fácil de equivocarse).
Si no quieres una clase completa para esto, otra forma simple es:
En un .cpp (no declares initblah en el .h)
// don't call this -- called by blahInited initialization
static bool InitBlah()
{
// init stuff here
return true;
}
bool blahInited = InitBlah();
Nadie puede llamarlo fuera de este .cpp, y se llama. Claro, alguien podría llamarlo en este .cpp: depende de cuánto le importe que sea imposible frente a inconveniente y documentado.
Si le importa el pedido o lo hace en un momento específico, entonces Singleton es probablemente para usted.
Hago exactamente eso todo el tiempo con situaciones que necesitan esa clase única pero no patrimonial-a-a-whole-class-para. Por supuesto, supone que no se preocupe por los problemas relacionados con los hilos. Por lo general, prefijo el nombre de la variable con "S_" para dejar en claro que es una variable estática.
Hmmm ... si no te opones a usar Impulsar, luego eche un vistazo a Boost :: Call_once:
namespace { boost::once_flag foo_init_flag = BOOST_ONCE_INIT; }
void InitFoo() {
// do stuff here
}
void FooCaller() {
boost::call_once(&foo_init_flag, InitFoo);
// InitFoo has been called exactly once!
}
void AnotherFooCaller() {
boost::call_once(&foo_init_flag, InitFoo);
// InitFoo has been called exactly once!
}
No es que esté muy entusiasmado con eso, pero esta es solo otra forma: Function Object.
#import <iostream>
class CallOnce {
private:
bool called;
public:
CallOnce() {
called = false;
}
void operator()(void) {
if (called) {
std::cout << "too many times, pal" <<std::endl;
return;
}
std::cout << "I was called!" << std::endl;
called = true;
}
};
int main(void) {
CallOnce call;
call();
call();
}