Pergunta

Se eu usar assert() e a afirmação falha então assert() chamará abort(), encerrando o programa em execução abruptamente.Não posso permitir isso no meu código de produção.Existe uma maneira de fazer afirmações em tempo de execução e ainda ser capaz de capturar afirmações com falha para que eu tenha a chance de lidar com elas normalmente?

Foi útil?

Solução

Sim, na verdade existe.Você mesmo precisará escrever uma função de afirmação personalizada, como o C++ assert() é exatamente C assert(), com o abort() "recurso" incluído.Felizmente, isso é surpreendentemente simples.

Afirmar.hh

template <typename X, typename A>
inline void Assert(A assertion)
{
    if( !assertion ) throw X();
}

A função acima lançará uma exceção se um predicado não for válido.Você terá então a chance de capturar a exceção.Se você não capturar a exceção, terminate() será chamado, o que encerrará o programa de forma semelhante a abort().

Você pode estar se perguntando que tal otimizar a afirmação quando estamos construindo para produção.Nesse caso, você pode definir constantes que significarão que você está construindo para produção e depois referir-se à constante quando você Assert().

depuração.hh

#ifdef NDEBUG
    const bool CHECK_WRONG = false;
#else
    const bool CHECK_WRONG = true;
#endif

principal.cc

#include<iostream>

struct Wrong { };

int main()
{
    try {
        Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5);
        std::cout << "I can go to sleep now.\n";
    }
    catch( Wrong e ) {
        std::cerr << "Someone is wrong on the internet!\n";
    }

    return 0;
}

Se CHECK_WRONG é uma constante então a chamada para Assert() será compilado na produção, mesmo que a afirmação não seja uma expressão constante.Há uma ligeira desvantagem em referir-se a CHECK_WRONG digitamos um pouco mais.Mas em troca ganhamos uma vantagem na medida em que podemos classificar vários grupos de asserções e ativar e desativar cada uma delas conforme acharmos adequado.Assim, por exemplo, poderíamos definir um grupo de asserções que queremos ativar mesmo no código de produção e, em seguida, definir um grupo de asserções que queremos ver apenas em compilações de desenvolvimento.

O Assert() função é equivalente a digitar

if( !assertion ) throw X();

mas indica claramente a intenção do programador:faça uma afirmação.As asserções também são mais fáceis de entender com esta abordagem, assim como assert()S.

Para obter mais detalhes sobre esta técnica, consulte The C++ Programming Language 3e de Bjarne Stroustrup, seção 24.3.7.2.

Outras dicas

funções de relatório de erros do glib adote a abordagem de continuar após uma afirmação.glib é a biblioteca subjacente de independência de plataforma que o Gnome (via GTK) usa.Aqui está uma macro que verifica uma pré-condição e imprime um rastreamento de pilha se a pré-condição falhar.

#define RETURN_IF_FAIL(expr)      do {                  \
 if (!(expr))                                           \
 {                                                      \
         fprintf(stderr,                                \
                "file %s: line %d (%s): precondition `%s' failed.", \
                __FILE__,                                           \
                __LINE__,                                           \
                __PRETTY_FUNCTION__,                                \
                #expr);                                             \
         print_stack_trace(2);                                      \
         return;                                                    \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                       \
 {                                                                  \
        fprintf(stderr,                                             \
                "file %s: line %d (%s): precondition `%s' failed.",     \
                __FILE__,                                               \
                __LINE__,                                               \
                __PRETTY_FUNCTION__,                                    \
                #expr);                                                 \
         print_stack_trace(2);                                          \
         return val;                                                    \
 };               } while(0)

Aqui está a função que imprime o rastreamento de pilha, escrito para um ambiente que usa o conjunto de ferramentas gnu (gcc):

void print_stack_trace(int fd)
{
    void *array[256];
    size_t size;

    size = backtrace (array, 256);
    backtrace_symbols_fd(array, size, fd);
}

É assim que você usaria as macros:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.

    if( ptr != NULL )        // Necessary if you want to define the macro only for debug builds
    {
       ...
    }

    return ptr;
}

void doSomethingElse(char *ptr)
{
    RETURN_IF_FAIL(ptr != NULL);
}

Afirmações em C/C++ são executadas apenas em compilações de depuração.Portanto, isso não acontecerá em tempo de execução.Em geral, as afirmações devem marcar coisas que, se acontecerem, indicam um bug e geralmente mostram suposições em seu código, etc.

Se você deseja ter um código que verifique erros em tempo de execução (no lançamento), você provavelmente deve usar exceções em vez de afirmações, pois é para isso que elas foram projetadas.Sua resposta basicamente envolve um lançador de exceção na sintaxe assert.Embora isso funcione, não há nenhuma vantagem específica que eu possa ver sobre apenas lançar a exceção em primeiro lugar.

Aqui está o que tenho em "assert.h" (Mac OS 10.4):

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__)))
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0)

Com base nisso, substitua a chamada para abort() por throw( exceção ).E em vez de printf você pode formatar a string na mensagem de erro da exceção.No final, você obtém algo assim:

#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
   std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))

Não tentei compilá-lo, mas você entendeu.

Observação:você precisará garantir que o cabeçalho "exceção" esteja sempre incluído, assim como o boost (se você decidir usá-lo para formatar a mensagem de erro).Mas você também pode transformar "my_assert" em uma função e declarar apenas seu protótipo.Algo como:

void my_assert( const char* e, const char* file, int line);

E implemente-o em algum lugar onde você possa incluir livremente todos os cabeçalhos necessários.

Envolva-o em algum #ifdef DEBUG se precisar, ou não, se quiser sempre executar essas verificações.

Se você quiser lançar uma sequência de caracteres com informações sobre a afirmação:http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

_set_error_mode(_OUT_TO_MSGBOX);

acredite, essa função pode te ajudar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top