Bagaimana cara membuang std :: exception dengan pesan variabel?

122

Ini adalah contoh dari apa yang sering saya lakukan ketika saya ingin menambahkan beberapa informasi ke pengecualian:

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

Apakah ada cara yang lebih baik untuk melakukannya?

Ben
sumber
11
Saya bertanya-tanya bagaimana Anda bahkan berhasil bekerja seperti ini - yang std∷exceptiontidak memiliki konstruktor dengan char*arg.
Hi-Angel
2
Saya bertanya-tanya hal yang sama. Mungkin ini adalah ekstensi MS non-standar untuk c ++? Atau mungkin sesuatu yang baru di C ++ 14? Dokumentasi saat ini mengatakan std :: exception constructor tidak mengambil argumen apa pun.
Chris Warth
1
Ya, tetapi std::stringmemiliki konstruktor implisit yang mengambil const char*...
Brice M. Dempsey
6
@Chris Warth Tampaknya menjadi bagian dari implementasi MS di belakang layar std::exceptionkelas anak, dan digunakan oleh versi std::runtime_errordan std::logic_error. Terlepas dari yang ditentukan oleh standar, versi MSVS <exception>juga mencakup dua lagi konstruktor, satu pengambilan (const char * const &)dan pengambilan lainnya (const char * const &, int). Mereka digunakan untuk mengatur variabel pribadi, const char * _Mywhat; jika _Mywhat != nullptr, maka secara what()default mengembalikannya. Kode yang bergantung padanya mungkin tidak portabel.
Waktu Justin - Kembalikan Monica

Jawaban:

49

Inilah solusi saya:

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

Contoh:

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
sumber
1
Ya Tuhan, aku sedang mencari cara untuk melakukan sesuatu seperti ini. Tetapi mungkin akan mengubah operator >> ke fungsi eksplisit untuk mencegah kelebihan- (kelebihan beban operator)
Roman Plášil
3
apa perbedaan antara this dan std :: stringstream? Tampaknya berisi aliran string, tetapi (sejauh yang saya tahu), tidak ada fungsi tambahan.
matts1
2
Secara umum, ini bukan cara yang 100% aman. std :: metode stringstream dapat memunculkan pengecualian Masalahnya cukup baik dijelaskan di sini: boost.org/community/error_handling.html
Arthur P. Golubev
1
@ ArthurP.Golubev Namun dalam kasus ini, sebuah instance Formatter () juga membuat instance stringstream di belakang layar, yang lagi-lagi dapat memunculkan pengecualian. Lalu apa bedanya?
Zuzu Corneliu
Fungsionalitas yang ditambahkan hanya trik ConvertToString dan cast eksplisit ke string, yang tetap bagus. ;)
Zuzu Corneliu
180

Pengecualian standar dapat dibuat dari 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);

Perhatikan bahwa kelas dasar tidakstd::exception bisa dibangun sehingga; Anda harus menggunakan salah satu kelas turunan yang konkret.

Kerrek SB
sumber
27

Ada pengecualian yang berbeda seperti runtime_error, range_error, overflow_error, logic_error, dll .. Anda harus lulus string ke konstruktor, dan Anda dapat menggabungkan apa pun yang Anda ingin pesan Anda. Itu hanya operasi string.

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

Anda juga bisa menggunakan boost::formatseperti ini:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
Neel Basu
sumber
Versi boost :: format di atas tidak akan bisa dikompilasi tanpa konversi eksplisit, yaitu: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 memperkenalkan std :: format yang akan menyediakan fungsionalitas serupa.
Digicrat
17

Kelas berikut mungkin cukup berguna:

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

Contoh penggunaan:

throw Error("Could not load config file '%s'", configfile.c_str());
Maxim Egorushkin
sumber
4
Praktik buruk IMO, mengapa menggunakan sesuatu seperti ini ketika sudah ada pustaka standar yang dibangun untuk pengoptimalan?
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);(mengonversi satu atau argumen lain menjadi std::stringjika perlu).
Mike Seymour
9
@MikeSeymour Ya, tapi itu menjadi lebih buruk jika Anda perlu meletakkan string di tengah dan memformat angka dengan presisi tertentu, dll. Sulit untuk mengalahkan string format lama yang bagus dalam hal kejelasan.
Maxim Egorushkin
2
@MikeSeymour Saya mungkin setuju bahwa kode yang saya posting mungkin lebih awal. Portably typesafe printfdan teman-teman akan segera hadir di C ++ 11. Buffer ukuran tetap adalah berkah dan kutukan: itu tidak gagal dalam situasi sumber daya rendah tetapi dapat memotong pesan. Saya menganggap memotong pesan kesalahan sebagai opsi yang lebih baik kemudian gagal. Selain itu, kenyamanan string format telah dibuktikan oleh banyak bahasa berbeda. Tetapi Anda benar, ini sebagian besar adalah masalah selera.
Maxim Egorushkin
11

Gunakan operator literal string jika C ++ 14 ( operator ""s)

using namespace std::string_literals;

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

atau tentukan sendiri jika dalam C ++ 11. Misalnya

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

Pernyataan lemparan Anda akan terlihat seperti ini

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

yang terlihat bagus dan bersih.

Shreevardhan
sumber
2
Saya mendapat kesalahan ini c ++ \ 7.3.0 \ bits \ exception.h | 63 | catatan: tidak ada fungsi yang cocok untuk panggilan ke 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir
Perilaku yang dijelaskan oleh @Shreevardhan tidak didefinisikan di pustaka std, meskipun MSVC ++ akan mengkompilasinya.
jochen
0

Cara yang lebih bagus adalah membuat kelas (atau kelas) untuk pengecualian.

Sesuatu seperti:

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

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

Alasannya adalah bahwa pengecualian jauh lebih disukai daripada hanya mentransfer string. Menyediakan kelas yang berbeda untuk kesalahan, Anda memberi pengembang kesempatan untuk menangani kesalahan tertentu dengan cara yang sesuai (tidak hanya menampilkan pesan kesalahan). Orang yang menangkap pengecualian Anda bisa sespesifik yang mereka butuhkan jika Anda menggunakan hierarki.

a) Seseorang mungkin perlu mengetahui alasan spesifiknya

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

a) orang lain tidak ingin mengetahui detailnya

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

Anda dapat menemukan beberapa inspirasi tentang topik ini di https://books.google.ru/books?id=6tjfmnKhT24C Bab 9

Selain itu, Anda juga dapat memberikan pesan khusus, tetapi hati-hati - tidak aman untuk membuat pesan dengan salah satu std::stringatau std::stringstreamatau cara lain yang dapat menyebabkan pengecualian .

Secara umum, tidak ada perbedaan apakah Anda mengalokasikan memori (bekerja dengan string dengan cara C ++) dalam konstruktor pengecualian atau tepat sebelum melempar - std::bad_allocpengecualian dapat dilemparkan sebelum yang Anda inginkan.

Jadi, buffer yang dialokasikan di stack (seperti dalam jawaban Maxim) adalah cara yang lebih aman.

Ini dijelaskan dengan sangat baik di http://www.boost.org/community/error_handling.html

Jadi, cara yang lebih baik adalah jenis pengecualian tertentu dan menghindari penulisan string yang diformat (setidaknya saat melempar).

Arthur P. Golubev
sumber
0

Mengalami masalah serupa, di mana membuat pesan kesalahan khusus untuk pengecualian khusus saya membuat kode jelek. Ini adalah solusi saya:

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

Ini memisahkan logika untuk membuat pesan. Saya awalnya berpikir untuk menimpa what (), tapi kemudian Anda harus menangkap pesan Anda di suatu tempat. std :: runtime_error sudah memiliki buffer internal.

sepeda
sumber
0

Mungkin ini?

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

Ini membuat ostringstream sementara, memanggil operator << seperlunya dan kemudian Anda membungkusnya dalam tanda kurung bulat dan memanggil fungsi .str () pada hasil yang dievaluasi (yang merupakan ostringstream) untuk meneruskan std :: string sementara ke konstruktor dari runtime_error.

Catatan: ostringstream dan string adalah temporer nilai-r dan keluar dari ruang lingkup setelah baris ini berakhir. Konstruktor objek pengecualian Anda HARUS mengambil string input menggunakan semantik salin atau pindah (lebih baik).

Tambahan: Saya tidak menganggap pendekatan ini sebagai "praktik terbaik", tetapi berhasil dan dapat digunakan dalam keadaan darurat. Salah satu masalah terbesar adalah bahwa metode ini memerlukan alokasi heap sehingga operator << dapat membuangnya. Anda mungkin tidak ingin itu terjadi; Namun, jika Anda masuk ke keadaan itu, Anda mungkin memiliki lebih banyak masalah untuk dikhawatirkan!

evilrix
sumber