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


121

这是我要向异常添加一些信息时经常执行的示例:

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

有更好的方法吗?


10
我想知道您如何设法以这种方式工作- std∷exception没有带有char*arg 的构造函数。
Hi-Angel

2
我在想同样的事情。也许这是对c ++的非标准MS扩展?还是C ++ 14中的新功能?当前文档说std :: exception构造函数不接受任何参数。
克里斯·沃斯

1
是的,但是std::string有一个隐式构造函数,它需要一个const char*...
Brice M. Dempsey

6
@克里斯瓦尔特这似乎是MS的'幕后的实现的一部分std::exception的子类,并且被他们的版本std::runtime_errorstd::logic_error。除了标准定义的<exception>构造器之外,MSVS的版本还包括另外两个构造函数,一个构造函数(const char * const &)和另一个构造函数(const char * const &, int)。它们用于设置私有变量const char * _Mywhat;如果为_Mywhat != nullptr,则what()默认返回它。依赖它的代码可能无法移植。
贾斯汀时间-恢复莫妮卡

Answers:


49

这是我的解决方案:

#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

1
我一直在寻找如何做这样的事情。但是可能会将操作符>>更改为显式函数,以防止过度(操作符重载)
RomanPlášil16

3
这和std :: stringstream有什么区别?它似乎包含一个stringstream,但是(据我所知)没有额外的功能。
matts1'9

2
通常,这不是100%安全的方法。std :: stringstream方法可能会引发异常。此处描述的问题相当不错:boost.org/community/error_handling.html
Arthur P. Golubev

1
@ ArthurP.Golubev但是在这种情况下,Formatter()实例还实例化了幕后的字符串流,这又可能引发异常。那有什么区别呢?
Zuzu Corneliu

唯一添加的功能是ConvertToString技巧和显式强制转换为字符串,无论如何还是不错的。;)
Zuzu Corneliu

178

可以从以下方式构造标准异常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可以被这样构成; 您必须使用一种具体的派生类。


27

有不同的例外,例如runtime_errorrange_erroroverflow_errorlogic_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);

如果没有显式转换,则上述boost :: format版本将无法编译,即:runtime_error((boost :: format(“ Text%1”%2).str()))。C ++ 20引入了std :: format,它将提供类似的功能。
Digicrat

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

4
海事组织不好的做法,当已经有一个为优化而建立的标准库时,为什么要使用这样的东西呢?
Jean-Marie Comets 2012年

3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Jean-Marie Comets 2012年

4
throw std::runtime_error("Could not load config file " + configfile);std::string如有必要,将一个或其他参数转换为)。
Mike Seymour 2012年

9
@MikeSeymour是的,但是如果您需要将字符串放在中间并以一定的精度格式化数字等会变得更糟。就清晰度而言,很难击败一个好的旧格式字符串。
Maxim Egorushkin 2012年

2
@MikeSeymour我可能同意我发布的代码可能早于其时间。可移植的类型安全printf,C ++ 11迫在眉睫。固定大小的缓冲区既是福也是祸:它在资源不足的情况下不会失败,但是可能会截断消息。我认为截断错误消息是更好的选择,然后失败。同样,格式字符串的便利性已被许多不同的语言证明。但是你是对的,这很大程度上取决于品味。
Maxim Egorushkin 2012年

11

如果C ++ 14(operator ""s),请使用字串文字运算子

using namespace std::string_literals;

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

或在C ++ 11中定义自己的语言。例如

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

看起来不错,干净。


2
我收到此错误c ++ \ 7.3.0 \ bits \ exception.h | 63 |注意:没有匹配的函数可以调用'std :: exception :: exception(std :: __ cxx11 :: basic_string <char>)
HaseeB Mir

@Shreevardhan描述的行为未在std库中定义,尽管MSVC ++会对其进行编译。
jochen

0

一种更好的方法是为异常创建一个或多个类。

就像是:

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

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

原因是异常比仅传输字符串更可取。为错误提供不同的类,使开发人员有机会以相应的方式处理特定的错误(而不仅仅是显示错误消息)。如果您使用层次结构,则捕获您的异常的人员可以根据他们的需要而定。

a)可能需要知道具体原因

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

a)另一个不想知道细节

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

您可以在以下位置找到有关此主题的一些启发 https://books.google.ru/books?id=6tjfmnKhT24C第9章中

此外,您也可以提供自定义消息,但要小心- std::string或组合消息并不安全std::stringstream多种可能导致异常的方式

通常,在异常的构造函数中还是在抛出之前分配内存(使用C ++方式使用字符串)都没有区别- std::bad_alloc抛出之前可以将异常抛出到真正想要的对象之前。

因此,在堆栈上分配一个缓冲区(如Maxim的回答)是一种更安全的方法。

http://www.boost.org/community/error_handling.html中对此进行了很好的解释

因此,更好的方法是特定类型的异常,并避免组成格式化的字符串(至少在抛出时)。


0

遇到类似的问题,因为为我的自定义异常创建自定义错误消息会产生难看的代码。这是我的解决方案:

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已经有一个内部缓冲区。


0

也许这个吗?

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.