152

这是我想向异常添加一些信息时经常做的一个例子:

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

有更好的方法吗?

4

9 回答 9

213

标准异常可以从以下构造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);

请注意,std::exception不能这样构造基类;您必须使用具体的派生类之一。

于 2012-09-04T10:50:51.370 回答
54

这是我的解决方案:

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

例子:

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
于 2012-09-04T11:32:52.047 回答
31

有不同的例外,例如runtime_error, range_error, overflow_error, logic_error等。您需要将字符串传递给它的构造函数,并且您可以将任何您想要的内容连接到您的消息中。这只是一个字符串操作。

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

你也可以boost::format这样使用:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
于 2012-09-04T10:53:39.567 回答
17

以下课程可能会非常方便:

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

使用示例:

throw Error("Could not load config file '%s'", configfile.c_str());
于 2012-09-04T10:53:35.573 回答
14

operator ""s如果 C++14 ( )则使用字符串文字运算符

using namespace std::string_literals;

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

或者在 C++11 中定义自己的 if。例如

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

然后您的 throw 语句将如下所示

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

看起来很干净。

于 2015-09-09T09:05:52.363 回答
4

也许这个?

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

它创建一个临时 ostringstream,根据需要调用 << 运算符,然后将其包装在圆括号中并在评估结果(这是一个 ostringstream)上调用 .str() 函数以将临时 std::string 传递给构造函数runtime_error 的。

注意:ostringstream 和 string 是 r-value 临时变量,因此在此行结束后超出范围。您的异常对象的构造函数必须使用复制或(更好的)移动语义来获取输入字符串。

附加:我不一定认为这种方法是“最佳实践”,但它确实有效并且可以在紧要关头使用。最大的问题之一是这种方法需要堆分配,因此运算符 << 可以抛出。您可能不希望这种情况发生;但是,如果您进入那种状态,您可能需要担心更多的问题!

于 2020-07-22T14:05:17.687 回答
2

关于你想要什么,有两点需要回答:

1.

第一点是更好的方法是为自定义异常创建特殊类型(类)并将参数作为类的字段传递。

类似于以下内容:

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

第二点是您在准备异常对象时正在执行内存分配,因为尝试传递可变大小(文件名)的值。

有可能(在更改 std::string 和 std::stringstream 的对象时)在它的过程中抛出 std::bad_alloc 异常,因此您无法准备或抛出(*)您的异常 -您将丢失信息和状态。

在设计良好的程序中,在准备或处理异常时很容易避免内存分配。您所需要的只是:

  • 要么在处理异常时保证该值仍然存在,并将某种链接作为异常的一部分传递给该值——要么是引用,要么是某种(很可能是智能的)指针,
  • 或在使用异常类型信息或/和固定大小值处理异常时获取值;例如,
} 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());

}

当然,您始终可以选择接受缓冲区大小限制并使用预先分配的缓冲区。

另外,请注意,用于异常的类型(类)不允许从其复制构造函数中抛出异常,因为如果尝试通过值捕获初始异常,则可以调用复制构造函数(如果不是被编译器忽略)并且这个额外的异常将在捕获初始异常之前中断初始异常处理,这会导致调用 std::terminate。由于允许 C++11 编译器在某些情况下在捕获时消除复制,但省略并不总是明智的,如果明智的话,它只是许可而不是义务(参见https://en.cppreference.com/ w/cpp/language/copy_elision了解详细信息;在 C++11 之前,该语言的标准并未规范此事)。

'*' 此外,您应该避免将异常(将它们称为附加)从构造函数中抛出,并移动用于异常的类型(类)的构造函数(将它们称为初始构造函数),因为可以调用构造函数和移动构造函数将类型的对象作为初始异常抛出,然后抛出一个额外的异常将阻止创建初始异常对象,并且初始异常对象将丢失。以及来自复制构造函数的附加异常,当抛出初始异常时,也会导致相同的结果。

于 2015-09-08T08:50:30.020 回答
0

每当我需要在异常中抛出自定义消息时,我都会构造一个 C 风格的字符串snprintf()并将其传递给异常构造函数。

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

我不确定是否需要额外的字符串string error_mesg(buffer)。我认为buffer它在堆栈内存上,如果异常捕获器继续运行,那么允许捕获器保留对堆栈分配的 C 字符串的引用是有问题的。相反,将 a 传递string给异常将调用按值复制,并且buffer数组将被深度复制。

于 2021-09-14T01:05:11.250 回答
-1

遇到了类似的问题,因为为我的自定义异常创建自定义错误消息会使代码变得丑陋。这是我的解决方案:

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

这将创建消息的逻辑分开。我最初考虑过覆盖what(),但是你必须在某个地方捕获你的消息。std::runtime_error 已经有一个内部缓冲区。

于 2020-07-15T16:21:40.433 回答