简体   繁体   English

如何使用可变消息抛出 std::exceptions?

[英]How to throw std::exceptions with variable messages?

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 file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

Is there a nicer way to do it?有没有更好的方法来做到这一点?

The standard exceptions can be constructed from a std::string :可以从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);

Note that the base class std::exception can not be constructed thus;注意基类std::exception不能这样构造; you have to use one of the concrete, derived classes.您必须使用具体的派生类之一。

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

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.有不同的异常,例如runtime_errorrange_erroroverflow_errorlogic_error等。您需要将字符串传递到其构造函数中,并且您可以将任何您想要的内容连接到您的消息中。 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:你也可以像这样使用boost::format

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

The following class might come quite handy:下面的类可能会派上用场:

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

Usage example:用法示例:

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

Use string literal operator if C++14 ( operator ""s )如果 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.或者在 C++11 中定义你自己的 if。 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 语句将如下所示

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

which looks nice and clean.看起来漂亮干净。

There are two points to answer in regards to what you want:关于你想要什么,有两点需要回答:

1. 1.

The first point is that the nicer way is creating special types (classes) for custom exceptions and passing parameters as fields of the classes.第一点是更好的方法是为自定义异常创建特殊类型(类)并将参数作为类的字段传递。

Something like the following:类似于以下内容:

class BaseFor_Exceptions : public std::exception {
protected:
    BaseFor_Exceptions();
};

class Exception1 : public BaseFor_Exceptions {
public:
    Exception1(uint32_t value1);
private:
    uint32_t value1;
};
throw Exception1(0);

The second point is that you are performing memory allocations when preparing the exception object because of trying to pass a value of variable size (filename).第二点是您在准备异常对象时正在执行内存分配,因为尝试传递可变大小(文件名)的值。

There is a possibility (when changing objects of the both std::string and std::stringstream ) of std::bad_alloc exception to be thrown in the process of it, so that you fail to prepare or throw (*) your exception – you will lose the info and the state.有可能(当更改 std::string 和 std::stringstream 的对象时)在处理过程中抛出 std::bad_alloc 异常,因此您无法准备或抛出(*)您的异常 –您将丢失信息和状态。

In a well-designed program it is easy to avoid memory allocation when preparing or handling an exception.在设计良好的程序中,在准备或处理异常时很容易避免内存分配。 All that you need is just:您只需要:

  • either guarantee the value is still life when handling the exception and pass some kind of link to the value as a part of the exception – either reference or some kind (most likely smart) of pointer,要么在处理异常时保证该值仍然存在,并将某种链接作为异常的一部分传递给该值——要么是引用,要么是某种(最有可能是智能的)指针,
  • or get the value when handling the exception using the exception type info or/and fixed-size values;或使用异常类型信息或/和固定大小值处理异常时获取值; for example,例如,
} catch (const ConfigurationLoadError & ex) {

    std::cerr
        << “Some message 1 ”
        << serviceLocator1.SomeGetMethod1().Get_ConfigurationFileName();

} catch (const SomeException & ex) {

    std::cerr
        << “Some message 2 ”
        << serviceLocator1.SomeGetMethod2().GetEventDetailsString(ex.Get_Value1());

}

Of course, you always have an option to accept buffer size limitations and use a pre-allocated buffer.当然,您始终可以选择接受缓冲区大小限制并使用预先分配的缓冲区。

Also, please note that the type (classes) used for exceptions are not permitted to throw exceptions out of their copy constructors since, if the initial exception is attempted to be caught by value, a call of copy constructor is possible (in case is not elided by the compiler) and this additional exception will interrupt the initial exception handling before the initial exception is caught, which causes calling std::terminate.另外,请注意,用于异常的类型(类)不允许从其复制构造函数中抛出异常,因为如果尝试通过值捕获初始异常,则可以调用复制构造函数(如果不是被编译器忽略)并且这个额外的异常将在初始异常被捕获之前中断初始异常处理,这会导致调用 std::terminate。 Since C++11 compilers are permitted to eliminate the copying in some cases when catching, but both the elision is not always sensible and, if sensible, it is only permission but not obligation (see https://en.cppreference.com/w/cpp/language/copy_elision for details; before C++11 the standards of the language didn't regulate the matter).由于 C++11 编译器在某些情况下被允许在捕获时消除复制,但是这两种省略并不总是明智的,如果明智,它只是许可而不是义务(参见https://en.cppreference.com/ w/cpp/language/copy_elision了解详情;在 C++11 之前,该语言的标准并没有规范此事)。

'*' Also, you should avoid exceptions (will call them the additional) to be thrown out of constructors and move constructors of your types (classes) used for exceptions (will call them initial) since the constructors and move constructors could be called when throwing objects of the types as initial exceptions, then throwing out an additional exception would prevent creation of an initial exception object, and the initial would just be lost. '*' 此外,您应该避免从构造函数中抛出异常(将它们称为附加的)并移动用于异常的类型(类)的构造函数(将它们称为初始构造函数),因为构造函数和移动构造函数可以在以下情况下被调用将类型的对象作为初始异常抛出,然后抛出额外的异常将阻止创建初始异常对象,并且初始异常对象将丢失。 As well as an additional exception from a copy constructor, when throwing an initial one, would cause the same.除了复制构造函数的额外异常之外,当抛出初始异常时,也会导致相同的异常。

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.它创建一个临时的 ostringstream,根据需要调用 << 运算符,然后将其包装在圆括号中,并在计算结果(这是一个 ostringstream)上调用 .str() 函数以将临时 std::string 传递给构造函数运行时错误。

Note: the ostringstream and the string are r-value temporaries and so go out of scope after this line ends.注意:ostringstream 和字符串是 r 值临时值,因此在此行结束后超出范围。 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!但是,如果您进入该状态,您可能需要担心更多问题!

Whenever I need a custom message to be thrown in an exception, I construct a C-style string with snprintf() and pass it to the exception constructor.每当我需要在异常中抛出自定义消息时,我都会使用snprintf()构造一个 C 样式的字符串并将其传递给异常构造函数。

if (problem_occurred) {
    char buffer[200];
    snprintf(buffer, 200, "Could not load config file %s", configfile);
    string error_mesg(buffer);
    throw std::runtime_error(error_mesg);
}

I'm not sure if the extra string string error_mesg(buffer) is necessary.我不确定是否需要额外的字符串string error_mesg(buffer) I reason that the buffer is on stack memory, and if the exception catcher keeps running, then allowing the catcher to keep a reference to a stack-allocated C string is problematic.我认为buffer在堆栈内存上,如果异常捕获器继续运行,那么允许捕获器保留对堆栈分配的 C 字符串的引用是有问题的。 Instead, passing a string to the exception will invoke copy-by-value, and the buffer array will be deep-copied.相反,将string传递给异常将调用按值复制,并且buffer数组将被深度复制。

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.我最初想覆盖 what(),但随后您必须在某处捕获您的消息。 std::runtime_error already has an internal buffer. std::runtime_error 已经有一个内部缓冲区。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM