How to throw std::exceptions with variable messages?

前端 未结 8 2084
梦如初夏
梦如初夏 2020-12-04 08:00

This is an example of what I often do when I want to add some information to an exception:

std::stringstream errMsg;
errMsg << \"Could not load config          


        
相关标签:
8条回答
  • 2020-12-04 08:35

    Use string literal operator if C++14 (operator ""s)

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

    or define your own if in C++11. For instance

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

    Your throw statement will then look like this

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

    which looks nice and clean.

    0 讨论(0)
  • 2020-12-04 08:36

    Maybe this?

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

    It creates a temporary ostringstream, calls the << operators as necessary and then you wrap that in round brackets and call the .str() function on the evaluated result (which is an ostringstream) to pass a temporary std::string to the constructor of runtime_error.

    Note: the ostringstream and the string are r-value temporaries and so go out of scope after this line ends. Your exception object's constructor MUST take the input string using either copy or (better) move semantics.

    Additional: I don't necessarily consider this approach "best practice", but it does work and can be used at a pinch. One of the biggest issues is that this method requires heap allocations and so the operator << can throw. You probably don't want that happening; however, if your get into that state your probably have way more issues to worry about!

    0 讨论(0)
  • 2020-12-04 08:38

    A really nicer way would be creating a class (or classes) for the exceptions.

    Something like:

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

    The reason is that exceptions are much more preferable than just transferring a string. Providing different classes for the errors, you give developers a chance to handle a particular error in a corresponded way (not just display an error message). People catching your exception can be as specific as they need if you use a hierarchy.

    a) One may need to know the specific reason

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

    a) another does not want to know details

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

    You can find some inspiration on this topic in https://books.google.ru/books?id=6tjfmnKhT24C Chapter 9

    Also, you can provide a custom message too, but be careful - it is not safe to compose a message with either std::string or std::stringstream or any other way which can cause an exception.

    Generally, there is no difference whether you allocate memory (work with strings in C++ manner) in the constructor of the exception or just before throwing - std::bad_alloc exception can be thrown before the one which you really want.

    So, a buffer allocated on the stack (like in Maxim's answer) is a safer way.

    It is explained very well at http://www.boost.org/community/error_handling.html

    So, the nicer way would be a specific type of the exception and be avoiding composing the formatted string (at least when throwing).

    0 讨论(0)
  • 2020-12-04 08:40

    Ran into a similar issue, in that creating custom error messages for my custom exceptions make ugly code. This was my solution:

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

    This separates the logic for creating the messages. I had originally thought about overriding what(), but then you have to capture your message somewhere. std::runtime_error already has an internal buffer.

    0 讨论(0)
  • 2020-12-04 08:42

    Here is my solution:

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

    Example:

    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
    
    0 讨论(0)
  • 2020-12-04 08:50

    There are different exceptions such as runtime_error, range_error, overflow_error, logic_error, etc.. You need to pass the string into its constructor, and you can concatenate whatever you want to your message. That's just a string operation.

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

    You can also use boost::format like this:

    throw std::runtime_error(boost::format("Error processing file %1") % fileName);
    
    0 讨论(0)
提交回复
热议问题