Değişken mesajlarla std :: istisnaları nasıl atılır?


122

Bu, bir istisnaya bazı bilgiler eklemek istediğimde sıklıkla yaptığım şeyin bir örneğidir:

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

Bunu yapmanın daha güzel bir yolu var mı?


11
Bu şekilde çalışmayı nasıl başardınız merak ediyorum - arg std∷exceptionile bir char*kurucuya sahip değilsiniz.
Hi-Angel

2
Ben de aynı şeyi merak ediyorum. Belki de c ++ için standart olmayan bir MS uzantısıdır? Veya belki C ++ 14'te yeni bir şey? Mevcut dokümantasyon std :: exception yapıcısının herhangi bir argüman almadığını söylüyor.
Chris Warth

1
Evet, ama std::stringbir sürer örtük bir Oluşturucu sahip const char*...
Brice M. Dempsey

6
@Chris Warth MS'in std::exceptionalt sınıflarının perde arkası uygulamasının bir parçası gibi görünüyor std::runtime_errorve ve std::logic_error. Apart standardıyla tanımlanır olanlardan ait MSVS' versiyonu <exception>da iki inşaatçı, bir alma içerir (const char * const &)ve diğer alma (const char * const &, int). Özel bir değişken ayarlamak için kullanılırlar const char * _Mywhat; eğer _Mywhat != nullptr, o zaman what()varsayılan olarak onu geri döndürür. Ona dayanan kod muhtemelen taşınabilir değildir.
Justin Time - Monica'yı yeniden

Yanıtlar:


49

İşte benim çözümüm:

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

Misal:

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

1
Aman tanrım, böyle bir şeyi nasıl yapacağımı araştırıyordum. Ama muhtemelen aşırı (operatör aşırı yüklemesini) önlemek için operatörü >> açık fonksiyona değiştirecek
Roman Plášil

3
bununla std :: stringstream arasındaki fark nedir? Görünüşe göre bir dizi akışı içeriyor, ancak (söyleyebildiğim kadarıyla) fazladan işlevselliği yok.
matts1

2
Genellikle% 100 güvenli bir yol değildir. std :: stringstream yöntemleri bir istisna atabilir. Sorun burada oldukça iyi açıklanmıştır: boost.org/community/error_handling.html
Arthur P. Golubev

1
@ ArthurP.Golubev Ancak bu durumda, bir Formatter () örneği de, yine bir istisna atabilen perde arkasında bir dizgi akışı başlatır. Öyleyse fark nedir?
Zuzu Corneliu

Eklenen tek işlevsellik, ConvertToString numarası ve yine de güzel olan dizeye açıkça dönüştürülmesidir. ;)
Zuzu Corneliu

180

Standart istisnalar şunlardan oluşturulabilir 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);

Temel sınıf Not std::exceptionolabilir olup bu şekilde inşa edilmesi; somut, türetilmiş sınıflardan birini kullanmanız gerekir.


27

Gibi farklı istisnalar vardır runtime_error, range_error, overflow_error, logic_error, vb .. Bunu yapıcı içine dize geçmesi gerekiyor ve size mesajınıza istediğini arada kullanabilirsiniz. Bu sadece bir dizi işlemi.

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

Ayrıca şu şekilde kullanabilirsiniz boost::format:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);

Yukarıdaki boost :: format sürümü, açık bir dönüştürme olmadan derlenmez, yani: runtime_error ((boost :: format ("Metin% 1"% 2) .str ())). C ++ 20, benzer işlevsellik sağlayacak bir std :: formatı sunar.
Digicrat

17

Aşağıdaki sınıf oldukça kullanışlı olabilir:

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

Kullanım örneği:

throw Error("Could not load config file '%s'", configfile.c_str());

4
Kötü uygulama IMO, optimizasyon için oluşturulmuş standart bir kitaplık varken neden böyle bir şey kullanasınız?
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);( std::stringgerekirse bir veya diğer argümanı dönüştürmek ).
Mike Seymour

9
@MikeSeymour Evet, ancak ortaya dizgileri koymanız ve sayıları belirli bir hassasiyetle biçimlendirmeniz gerektiğinde, bu daha da çirkinleşiyor. Açıklık açısından eski bir biçim dizgisini yenmek zor.
Maxim Egorushkin

2
@MikeSeymour Gönderdiğim kodun zamanının ötesinde olabileceğini kabul edebilirim. Taşınabilir tipte güvenli printfve arkadaşlar C ++ 11'de çok yakında. Sabit boyutlu arabellek hem bir nimet hem de bir lanettir: kaynakların yetersiz olduğu durumlarda başarısız olmaz ancak mesajı kesebilir. Bir hata mesajını kesmenin başarısız olmaktan daha iyi bir seçenek olduğunu düşünüyorum. Ayrıca, biçim dizgelerinin uygunluğu birçok farklı dil tarafından kanıtlanmıştır. Ama haklısın, bu büyük ölçüde bir zevk meselesi.
Maxim Egorushkin

11

C ++ 14 ( operator ""s) ise dize değişmez işleci kullanın

using namespace std::string_literals;

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

veya C ++ 11'de ise kendiniz tanımlayın. Örneğin

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

Atma ifadeniz daha sonra böyle görünecek

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

güzel ve temiz görünüyor.


2
Bu hatayı aldım c ++ \ 7.3.0 \ bits \ exception.h | 63 | not: 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>) çağrısı için eşleşen işlev yok
HaseeB Mir

@Shreevardhan tarafından açıklanan davranış, standart kitaplıkta tanımlanmamıştır, ancak MSVC ++ bunu derleyecektir.
jochen

0

Gerçekten daha hoş bir yol, istisnalar için bir sınıf (veya sınıflar) oluşturmaktır.

Gibi bir şey:

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

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

Bunun nedeni, istisnaların sadece bir dizeyi transfer etmekten çok daha fazla tercih edilmesidir. Hatalar için farklı sınıflar sağlayarak, geliştiricilere belirli bir hatayı karşılık gelen bir şekilde ele alma şansı verirsiniz (sadece bir hata mesajı göstermez). Bir hiyerarşi kullanıyorsanız, istisnanızı yakalayan kişiler ihtiyaç duydukları kadar spesifik olabilir.

a) Kişinin belirli nedeni bilmesi gerekebilir

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

a) başkası ayrıntıları bilmek istemiyor

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

Bu konuyla ilgili bazı ilhamları https://books.google.ru/books?id=6tjfmnKhT24C adresinde bulabilirsiniz. Bölüm 9'da bulabilirsiniz.

Ayrıca, çok özel bir mesaj sağlamak, ancak dikkatli olun - bir ileti oluşturmak için güvenli değil ya std::stringya std::stringstreamya da bir istisna neden olabilir başka bir yolu .

Genel olarak, istisnanın yapıcısına bellek ayırmanız (C ++ tarzında dizelerle çalışın) veya atmadan hemen önce fark yoktur - std::bad_allocistisna, gerçekten istediğiniz olandan önce atılabilir.

Bu nedenle, yığına ayrılan bir tampon (Maxim'in cevabında olduğu gibi) daha güvenli bir yoldur.

Http://www.boost.org/community/error_handling.html adresinde çok iyi açıklanmıştır.

Bu nedenle, daha güzel yol, özel bir istisna türü olabilir ve biçimlendirilmiş dizeyi oluşturmaktan kaçınmaktır (en azından atarken).


0

Benzer bir sorunla karşılaştım, çünkü benim özel istisnalarım için özel hata mesajları oluşturmanın çirkin kodlar oluşturması. Bu benim çözümümdü:

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

Bu, mesajları oluşturma mantığını ayırır. Başlangıçta neyi () geçersiz kılmayı düşünmüştüm, ama sonra mesajınızı bir yerde yakalamanız gerekiyor. std :: runtime_error zaten dahili bir arabelleğe sahip.


0

Belki bu?

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

Geçici bir ostringstream oluşturur, << işleçlerini gerektiği gibi çağırır ve sonra bunu yuvarlak parantez içine alırsınız ve kurucuya geçici bir std :: string iletmek için değerlendirilen sonuçta (bir ostringstream) .str () işlevini çağırırsınız. of runtime_error.

Not: ostringstream ve string, r-değeri geçicileridir ve bu nedenle bu satır bittikten sonra kapsam dışına çıkar. İstisna nesnenizin kurucusunun girdi dizesini kopyalama veya (daha iyi) taşıma semantiğini kullanarak alması ZORUNLUdur.

Ek: Bu yaklaşımı mutlaka "en iyi uygulama" olarak düşünmüyorum, ancak işe yarıyor ve bir çimdikle kullanılabilir. En büyük sorunlardan biri, bu yöntemin yığın ayırmaları gerektirmesi ve bu nedenle << operatörünün atabilmesidir. Muhtemelen bunun olmasını istemezsiniz; ancak, bu duruma girerseniz muhtemelen endişelenmeniz gereken daha çok sorun vardır!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.