Jak wyrzucić wyjątki std :: z komunikatami zmiennymi?

121

Oto przykład tego, co często robię, gdy chcę dodać informacje do wyjątku:

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

Czy jest na to lepszy sposób?

Ben
źródło
10
Zastanawiam się, jak w ogóle udało Ci się tak pracować - std∷exceptionnie masz konstruktora z char*arg.
Hi-Angel
2
Zastanawiam się nad tym samym. Może jest to niestandardowe rozszerzenie MS do c ++? A może coś nowego w C ++ 14? Obecna dokumentacja mówi, że konstruktor std :: wyjątek nie przyjmuje żadnych argumentów.
Chris Warth
1
Tak, ale std::stringma domyślnego konstruktora, który pobiera const char*...
Brice M. Dempsey
6
@Chris Warth Wygląda na to, że jest częścią zakulisowej implementacji std::exceptionklas podrzędnych firmy MS i jest używany przez ich wersje std::runtime_errori std::logic_error. Oprócz tych zdefiniowanych przez standard, wersja MSVS <exception>zawiera jeszcze dwa konstruktory, jeden bierze, (const char * const &)a drugi bierze (const char * const &, int). Są używane do ustawiania zmiennej prywatnej const char * _Mywhat,; jeśli _Mywhat != nullptr, to what()domyślnie zwraca go. Kod, który się na nim opiera, prawdopodobnie nie jest przenośny.
Justin Time - Przywróć Monikę

Odpowiedzi:

49

Oto moje rozwiązanie:

#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 &);
};

Przykład:

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
źródło
1
omg szukałem, jak zrobić coś takiego. Ale prawdopodobnie zmieni operator >> na jawną funkcję, aby zapobiec przeładowaniu (przeciążeniu operatora)
Roman Plášil
3
jaka jest różnica między this a std :: stringstream? Wydaje się, że zawiera ciąg ciągów, ale (o ile wiem) nie ma żadnych dodatkowych funkcji.
matts1
2
Generalnie nie jest to w 100% bezpieczny sposób. Metody std :: stringstream mogą zgłosić wyjątek, problem jest dość dobrze opisany tutaj: boost.org/community/error_handling.html
Arthur P. Golubev
1
@ ArthurP.Golubev Ale w tym przypadku instancja Formatter () również tworzy instancję stringstream za kulisami, co znowu może zgłosić wyjątek. Więc jaka jest różnica?
Zuzu Corneliu
Jedyną dodaną funkcjonalnością jest sztuczka ConvertToString i jawne rzutowanie na ciąg, co i tak jest fajne. ;)
Zuzu Corneliu
178

Standardowe wyjątki można zbudować z 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);

Zauważ, że klasa bazowa niestd::exception może zostać w ten sposób skonstruowana; musisz użyć jednej z konkretnych, pochodnych klas.

Kerrek SB
źródło
27

Istnieją różne wyjątki, takie jak runtime_error, range_error, overflow_error, logic_error, etc .. Trzeba przekazać ciąg do jego konstruktora, a można łączyć, co chcesz do wiadomości. To tylko operacja na łańcuchu.

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

Możesz także użyć w boost::formatten sposób:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
Neel Basu
źródło
Powyższa wersja boost :: format nie skompiluje się bez jawnej konwersji, tj .: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 wprowadza format std ::, który zapewni podobną funkcjonalność.
Digicrat
17

Następująca klasa może się bardzo przydać:

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; }
};

Przykład użycia:

throw Error("Could not load config file '%s'", configfile.c_str());
Maxim Egorushkin
źródło
4
Zła praktyka IMO, po co używać czegoś takiego, skoro istnieje już standardowa biblioteka zbudowana do optymalizacji?
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);(w std::stringrazie potrzeby konwertowanie jednego lub innego argumentu na ).
Mike Seymour
9
@MikeSeymour Tak, ale to staje się brzydsze, jeśli musisz umieścić ciągi znaków w środku i sformatować liczby z określoną precyzją itp. Trudno jest pokonać dobry, stary format, jeśli chodzi o przejrzystość.
Maxim Egorushkin
2
@MikeSeymour Mogę się zgodzić, że opublikowany przeze mnie kod może wyprzedzać swoje czasy. Przenośne bezpieczeństwo printfi przyjaciele są nieuchronni w C ++ 11. Bufor o stałym rozmiarze jest zarówno błogosławieństwem, jak i przekleństwem: nie zawodzi w sytuacjach niskiego poziomu zasobów, ale może obciąć wiadomość. Uważam, że skrócenie komunikatu o błędzie jest lepszą opcją niż niepowodzenie. Również wygoda ciągów formatujących została udowodniona w wielu różnych językach. Ale masz rację, to w dużej mierze kwestia gustu.
Maxim Egorushkin
11

Użyj operatora literału ciągu, jeśli C ++ 14 ( operator ""s)

using namespace std::string_literals;

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

lub zdefiniuj własne, jeśli w C ++ 11. Na przykład

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

Twoje oświadczenie o rzucie będzie wtedy wyglądać tak

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

który wygląda ładnie i czysto.

Shreevardhan
źródło
2
Otrzymałem ten błąd c ++ \ 7.3.0 \ bits \ception.h | 63 | uwaga: brak funkcji dopasowującej dla wywołania 'std :: wyjątek :: wyjątku (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir
Zachowanie opisane przez @Shreevardhan nie jest zdefiniowane w bibliotece std, chociaż MSVC ++ je skompiluje.
jochen
0

Naprawdę przyjemniejszym sposobem byłoby utworzenie klasy (lub klas) dla wyjątków.

Coś jak:

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

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

Powodem jest to, że wyjątki są znacznie lepsze niż zwykłe przesyłanie łańcucha. Zapewniając różne klasy dla błędów, dajesz programistom szansę na obsłużenie określonego błędu w odpowiedni sposób (a nie tylko wyświetlenie komunikatu o błędzie). Jeśli korzystasz z hierarchii, osoby wychwytujące Twój wyjątek mogą być tak szczegółowe, jak potrzebują.

a) Być może trzeba będzie znać konkretny powód

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

a) inny nie chce znać szczegółów

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

Inspirację na ten temat można znaleźć w https://books.google.ru/books?id=6tjfmnKhT24C Rozdział 9

Ponadto, można dostarczyć niestandardowy komunikat zbyt, ale należy zachować ostrożność - nie jest to bezpieczne, aby utworzyć wiadomość z albo std::stringalbo std::stringstreamalbo jakikolwiek inny sposób, który może spowodować wyjątek .

Generalnie nie ma różnicy, czy alokujesz pamięć (pracujesz z napisami w sposób C ++) w konstruktorze wyjątku, czy tuż przed wyrzuceniem - std::bad_allocwyjątek można wyrzucić przed tym, który naprawdę chcesz.

Tak więc bufor przydzielony na stosie (jak w odpowiedzi Maxima) jest bezpieczniejszym sposobem.

Jest to bardzo dobrze wyjaśnione na http://www.boost.org/community/error_handling.html

Więc lepszym sposobem byłby określony typ wyjątku i unikanie tworzenia sformatowanego ciągu (przynajmniej podczas rzucania).

Arthur P. Golubev
źródło
0

Napotkałem podobny problem, ponieważ tworzenie niestandardowych komunikatów o błędach dla moich niestandardowych wyjątków powoduje brzydki kod. To było moje rozwiązanie:

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
     }
}

To oddziela logikę tworzenia wiadomości. Początkowo myślałem o nadpisaniu what (), ale potem musisz gdzieś przechwycić swoją wiadomość. std :: runtime_error ma już wewnętrzny bufor.

bpeikes
źródło
0

Może to?

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

Tworzy tymczasowy strumień ostringstream, w razie potrzeby wywołuje operatory <<, a następnie zawijasz go w nawiasy okrągłe i wywołujesz funkcję .str () na ocenianym wyniku (którym jest strumień ostringstream), aby przekazać tymczasowy std :: string do konstruktora błędu runtime_error.

Uwaga: strumień ostringstream i string są tymczasowymi wartościami r, więc po zakończeniu tej linii wychodzą poza zakres. Konstruktor obiektu wyjątku MUSI pobrać ciąg wejściowy przy użyciu semantyki kopiowania lub (lepiej) przenoszenia.

Dodatkowe: niekoniecznie uważam to podejście za „najlepszą praktykę”, ale działa i można je stosować w mgnieniu oka. Jednym z największych problemów jest to, że ta metoda wymaga alokacji sterty, więc operator << może rzucać. Prawdopodobnie nie chcesz, aby tak się stało; Jednak jeśli dojdziesz do tego stanu, prawdopodobnie będziesz miał więcej problemów do zmartwienia!

evilrix
źródło