Como lançar std :: exceções com mensagens variáveis?

121

Este é um exemplo do que costumo fazer quando desejo adicionar algumas informações a uma exceção:

std::stringstream errMsg;
errMsg << "Could not load config file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

Existe uma maneira melhor de fazer isso?

Ben
fonte
10
Eu estou me perguntando como você conseguiu trabalhar dessa forma - std∷exceptionnão tem um construtor com char*arg.
Hi-Angel
2
Estou me perguntando a mesma coisa. Talvez seja uma extensão MS não padrão para c ++? Ou talvez algo novo em C ++ 14? A documentação atual diz que o construtor std :: exception não aceita nenhum argumento.
Chris Warth
1
Sim, mas std::stringtem um construtor implícito que leva um const char*...
Brice M. Dempsey
6
@Chris Warth Parece ser parte da implementação nos bastidores da MS das std::exceptionclasses filhas de , e é usado por suas versões de std::runtime_errore std::logic_error. Além dos definidos pelo padrão, a versão do MSVS de <exception>também inclui mais dois construtores, um pegando (const char * const &)e outro pegando (const char * const &, int). Eles são usados ​​para definir uma variável privada const char * _Mywhat,; se _Mywhat != nullptr, então o what()padrão é retorná-lo. O código que depende dele provavelmente não é portátil.
Hora de Justin - Reintegrar Monica

Respostas:

49

Aqui está minha solução:

#include <stdexcept>
#include <sstream>

class Formatter
{
public:
    Formatter() {}
    ~Formatter() {}

    template <typename Type>
    Formatter & operator << (const Type & value)
    {
        stream_ << value;
        return *this;
    }

    std::string str() const         { return stream_.str(); }
    operator std::string () const   { return stream_.str(); }

    enum ConvertToString 
    {
        to_str
    };
    std::string operator >> (ConvertToString) { return stream_.str(); }

private:
    std::stringstream stream_;

    Formatter(const Formatter &);
    Formatter & operator = (Formatter &);
};

Exemplo:

throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData);   // implicitly cast to std::string
throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData >> Formatter::to_str);    // explicitly cast to std::string
Torsten
fonte
1
omg, estive procurando como fazer algo assim. Mas provavelmente mudará operador >> para função explícita para evitar excesso - (sobrecarga de operador)
Roman Plášil
3
qual é a diferença entre isso e um std :: stringstream? Parece conter um stringstream, mas não tem (pelo que eu sei) nenhuma funcionalidade extra.
matts1
2
Geralmente, não é uma forma 100% segura. Os métodos std :: stringstream podem lançar uma exceção. O problema é muito bom descrito aqui: boost.org/community/error_handling.html
Arthur P. Golubev
1
@ ArthurP.Golubev Mas, neste caso, uma instância de Formatter () também instancia um stringstream nos bastidores, o que, novamente, poderia lançar uma exceção. Qual é a diferença?
Zuzu Corneliu
A única funcionalidade adicionada é o truque ConvertToString e a conversão explícita em string, o que é bom de qualquer maneira. ;)
Zuzu Corneliu
178

As exceções padrão podem ser construídas a partir de std::string:

#include <stdexcept>

char const * configfile = "hardcode.cfg";
std::string const anotherfile = get_file();

throw std::runtime_error(std::string("Failed: ") + configfile);
throw std::runtime_error("Error: " + anotherfile);

Observe que a classe base nãostd::exception pode ser construída dessa forma; você tem que usar uma das classes derivadas concretas.

Kerrek SB
fonte
27

Existem diferentes exceções, como runtime_error, range_error, overflow_error, logic_error, etc .. Você precisa passar a corda em seu construtor, e você pode concatenar o que quiser à sua mensagem. Isso é apenas uma operação de string.

std::string errorMessage = std::string("Error: on file ")+fileName;
throw std::runtime_error(errorMessage);

Você também pode usar boost::formatdesta forma:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
Neel Basu
fonte
A versão boost :: format acima não compilará sem uma conversão explícita, ou seja: runtime_error ((boost :: format ("Texto% 1"% 2) .str ())). C ++ 20 apresenta um formato std :: que fornecerá funcionalidade semelhante.
Digicrat
17

A seguinte classe pode ser muito útil:

struct Error : std::exception
{
    char text[1000];

    Error(char const* fmt, ...) __attribute__((format(printf,2,3))) {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(text, sizeof text, fmt, ap);
        va_end(ap);
    }

    char const* what() const throw() { return text; }
};

Exemplo de uso:

throw Error("Could not load config file '%s'", configfile.c_str());
Maxim Egorushkin
fonte
4
IMO de má prática, por que usar algo assim quando já existe uma biblioteca padrão que é construída para otimização?
Jean-Marie Comets
3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Jean-Marie Comets
4
throw std::runtime_error("Could not load config file " + configfile);(convertendo um ou outro argumento para, std::stringse necessário).
Mike Seymour
9
@MikeSeymour Sim, mas isso fica mais feio se você precisar colocar strings no meio e formatar números com certa precisão, etc. É difícil superar uma string de formato antigo em termos de clareza.
Maxim Egorushkin
2
@MikeSeymour Posso concordar que o código que postei pode estar à frente de seu tempo. Portably typesafe printfe amigos são iminentes no C ++ 11. O buffer de tamanho fixo é uma bênção e uma maldição: ele não falha em situações de poucos recursos, mas pode truncar a mensagem. Considero truncar uma mensagem de erro uma opção melhor do que falhar. Além disso, a conveniência das strings de formato foi comprovada por muitas linguagens diferentes. Mas você está certo, é em grande parte uma questão de gosto.
Maxim Egorushkin
11

Use o operador literal de string se C ++ 14 ( operator ""s)

using namespace std::string_literals;

throw std::exception("Could not load config file '"s + configfile + "'"s);

ou defina seu próprio se estiver em C ++ 11. Por exemplo

std::string operator ""_s(const char * str, std::size_t len) {
    return std::string(str, str + len);
}

Sua declaração de lançamento ficará assim

throw std::exception("Could not load config file '"_s + configfile + "'"_s);

que parece bom e limpo.

Shreevardhan
fonte
2
Recebi este erro c ++ \ 7.3.0 \ bits \ exception.h | 63 | nota: nenhuma função correspondente para chamada de 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir
O comportamento descrito por @Shreevardhan não está definido na biblioteca std, embora MSVC ++ vá compilá-lo.
jochen,
0

Uma maneira realmente melhor seria criar uma classe (ou classes) para as exceções.

Algo como:

class ConfigurationError : public std::exception {
public:
    ConfigurationError();
};

class ConfigurationLoadError : public ConfigurationError {
public:
    ConfigurationLoadError(std::string & filename);
};

O motivo é que as exceções são muito mais preferíveis do que apenas transferir uma string. Fornecendo classes diferentes para os erros, você dá aos desenvolvedores a chance de lidar com um erro específico de uma forma correspondente (não apenas exibir uma mensagem de erro). As pessoas que detectam sua exceção podem ser tão específicas quanto precisam se você usar uma hierarquia.

a) Pode ser necessário saber o motivo específico

} catch (const ConfigurationLoadError & ex) {
// ...
} catch (const ConfigurationError & ex) {

a) outro não quer saber detalhes

} catch (const std::exception & ex) {

Você pode encontrar inspiração neste tópico no https://books.google.ru/books?id=6tjfmnKhT24C Capítulo 9

Além disso, você pode fornecer uma mensagem personalizada também, mas tenha cuidado - não é seguro para compor uma mensagem com qualquer um std::stringou std::stringstreamou qualquer outra forma que pode causar uma exceção .

Geralmente, não há diferença se você aloca memória (trabalha com strings no modo C ++) no construtor da exceção ou imediatamente antes de lançar - a std::bad_allocexceção pode ser lançada antes daquela que você realmente deseja.

Portanto, um buffer alocado na pilha (como na resposta de Maxim) é uma maneira mais segura.

É muito bem explicado em http://www.boost.org/community/error_handling.html

Então, a maneira mais legal seria um tipo específico de exceção e evitar a composição da string formatada (pelo menos ao lançar).

Arthur P. Golubev
fonte
0

Tive um problema semelhante, pois a criação de mensagens de erro personalizadas para minhas exceções personalizadas tornava o código feio. Esta foi a minha solução:

class MyRunTimeException: public std::runtime_error
{
public:
      MyRunTimeException(const std::string &filename):std::runtime_error(GetMessage(filename)) {}
private:
      static std::string GetMessage(const std::string &filename)
     {
           // Do your message formatting here. 
           // The benefit of returning std::string, is that the compiler will make sure the buffer is good for the length of the constructor call
           // You can use a local std::ostringstream here, and return os.str()
           // Without worrying that the memory is out of scope. It'll get copied
           // You also can create multiple GetMessage functions that take all sorts of objects and add multiple constructors for your exception
     }
}

Isso separa a lógica para criar as mensagens. Eu tinha pensado originalmente em substituir what (), mas então você tem que capturar sua mensagem em algum lugar. std :: runtime_error já tem um buffer interno.

bicicletas
fonte
0

Talvez isto?

throw std::runtime_error(
    (std::ostringstream()
        << "Could not load config file '"
        << configfile
        << "'"
    ).str()
);

Ele cria um ostringstream temporário, chama os operadores << conforme necessário e então você envolve isso entre colchetes e chama a função .str () no resultado avaliado (que é um ostringstream) para passar uma std :: string temporária para o construtor de runtime_error.

Observação: o ostringstream e a string são temporários com valor r e, portanto, saem do escopo após o término desta linha. O construtor do seu objeto de exceção DEVE receber a string de entrada usando cópia ou (melhor) semântica de movimentação.

Adicional: não considero necessariamente essa abordagem como "prática recomendada", mas ela funciona e pode ser usada em uma pitada. Um dos maiores problemas é que esse método requer alocações de heap e, portanto, o operador << pode lançar. Você provavelmente não quer que isso aconteça; no entanto, se você entrar nesse estado, provavelmente terá muito mais problemas com que se preocupar!

evilrix
fonte