Guard de chamada de função
Pergunta
Suponha que eu tenha uma função gratuita chamada InitFoo
. Eu gostaria de proteger essa função de ser chamado várias vezes por acidente. Sem pensar muito, escrevi o seguinte:
void InitFoo()
{
{
static bool flag = false;
if(flag) return;
flag = true;
}
//Actual code goes here.
}
Parece uma verruga grande, no entanto. InitFoo
faz não precisa preservar qualquer outra informação do estado. Alguém pode sugerir uma maneira de alcançar o mesmo objetivo sem a feiúra?
As macros não contam, é claro.
Solução
Você pode fazer isso com alguma feiura diferente:
struct InitFoo
{
InitFoo()
{
// one-time code goes here
}
};
void Foo()
{
static InitFoo i;
}
Você ainda está usando static
, mas agora você não precisa fazer sua própria verificação de bandeira - static
já coloca uma bandeira e um cheque, então só constrói i
uma vez.
Outras dicas
Bem, um construtor é chamado apenas automaticamente uma vez. Se você criar uma única instância desta classe:
class Foo
{
public:
Foo(void)
{
// do stuff
}
}
Então //do stuff
só será executado uma vez. A única maneira de executá -lo duas vezes é criar outra instância da classe.
Você pode evitar isso usando um Singleton. Na verdade, //do stuff
só pode ser chamado uma vez.
Eu gostaria de proteger essa função de ser chamado várias vezes por acidente
Para mim, isso parece um problema que só surgirá durante a depuração. Se for esse o caso, eu simplesmente faria o seguinte:
void InitFoo()
{
#ifndef NDEBUG
static bool onlyCalledOnce = TRUE;
assert(onlyCalledOnce);
onlyCalledOnce = FALSE;
#endif
...
}
O objetivo desta verruga em particular é facilmente discernido apenas olhando para ela, e causará uma falha agradável, grande e chamativa se um programador cometer o erro de ligar InitFoo
mais de uma vez. Também vai desaparecer completamente no código de produção. (quando NDEBUG
é definido).
editar: Uma nota rápida sobre a motivação:
Chamar uma função init mais de uma vez é provavelmente um grande erro. Se o usuário final dessa função o chamou por engano duas vezes, ignorar silenciosamente esse erro provavelmente não é o caminho a seguir. Se você não for o assert()
rota, eu recomendaria pelo menos despejar uma mensagem para stdout
ou stderr
.
É exatamente assim que eu faria. Você pode usar algum ponteiro de função se arrastando se quiser uma alternativa:
static void InitFoo_impl()
{
// Do stuff.
// Next time InitFoo is called, call abort() instead.
InitFoo = &abort;
}
void (*InitFoo)() = &InitFoo_impl;
Você também precisa ser um seguro com vários thread? Olhe para o padrão de singleton com bloqueio de check duas vezes (o que é surpreendente fácil de errar).
Se você não quer uma aula inteira para isso, outra maneira simples é:
Em um .cpp (não declare initblah no .h)
// don't call this -- called by blahInited initialization
static bool InitBlah()
{
// init stuff here
return true;
}
bool blahInited = InitBlah();
Ninguém pode chamá -lo fora deste .CPP, e ele é chamado. Claro, alguém poderia chamá -lo neste .cpp - depende de quanto você se importa que seja impossível versus inconveniente e documentado.
Se você se preocupa com o pedido ou fazê -lo em um horário específico, Singleton provavelmente é para você.
Faço exatamente isso o tempo todo com situações que precisam daquela classificação única, mas não o valor, que não fazia uma classe. Obviamente, pressupõe que você não se preocupe com problemas relacionados a linhas. Normalmente, prefixo o nome da variável com "S_" para deixar claro que é uma variável estática.
Hmmm ... se você não se opõe a usar Impulso, então dê uma olhada em 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!
}
Não que eu esteja muito empolgado com isso, mas isso é apenas outra maneira: o objeto de função.
#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();
}