区分同一类型的多个异常

时间:2015-08-04 08:14:45

标签: c++ c++11 exception exception-handling

我无法完全理解用户如何能够区分我的函数可以抛出的异常。我的一个函数可以抛出两个std::invalid_argument的实例。

例如,在构造函数中:

#include <stdexcept> // std::invalid_argument
#include <string>

class Foo
{
public:
    void Foo(int hour, int minute)
    :h(hour), m(minute)
    {
        if(hour < 0 || hour > 23)
            throw std::invalid_argument(std::string("..."));
        if(minute < 0 || minute > 59)
            throw std::invalid_argument(std::string("..."));
    }
}

注意:这是一个例子,请不要用有界整数来回答。

假设用户使用foo(23, 62);进行调用,用户的异常处理程序如何区分std::invalid_argument的两个可能实例?

或者我做错了,我应该继承std :: invalid_argument来区分它们吗?也就是说,

class InvalidHour: public std::invalid_argument
{
public:
    InvalidHour(const std::string& what_arg)
    :std::invalid_argument(msg) {};
}

class InvalidMinute: public std::invalid_argument
{
public:
    InvalidMinute(const std::string& what_arg)
    :std::invalid_argument(msg) {};
}

然后,抛出InvalidHourInvalidMinute

编辑:为每个可能的异常创建一个类似乎对我来说有点太多了,特别是在大型程序中。每个以这种方式有效使用异常的程序是否都有关于要捕获的内容的大量文档?

如答案所述,我考虑过assert。看看stackoverflow,我发现大多数人都说你应该抛出异常(因为我的特殊情况是构造函数)。

在查看了许多关于何时使用异常的在线信息之后,普遍的共识是使用assert来解决运行时错误的逻辑错误和异常。虽然,使用无效参数 调用foo(int, int)可能 是运行时错误。这就是我想要解决的问题。

4 个答案:

答案 0 :(得分:4)

标准异常层次结构不适用于逻辑错误。使用assert并完成它。如果你绝对想要将硬错误转换为更难以检测运行时错误,那么请注意,处理程序只能执行两个合理的事情:以某种可能不同的方式实现合同目标(可能只是重试操作),或者抛出一个异常(通常只是重新抛出),并且原始异常的确切原因很少发挥作用。最后,如果你确实想要支持那些真正尝试各种参数组合的代码,直到它找到一个不抛出的代码,无论现在看起来多么愚蠢,它都是用文字写出来的,那么你有std::system_error传递整数错误代码,但可以定义派生的异常类。

所有这一切,请转到assert

这就是它的用途。

答案 1 :(得分:1)

您还可以创建从invalid_argument派生的更多错误类,这将使它们可以区分,但这不是可扩展的解决方案。如果您真正想要的是向suer显示他可以理解的消息,那么invalid_argument的字符串参数将用于此目的。

答案 2 :(得分:1)

标准异常不允许存储您想要的其他信息,解析异常消息是个坏主意。正如您所提到的,一种解决方案是子类化。还有其他人 - 随着std::exception_ptr的出现。可以使用Java或.NET中的“内部”(或“嵌套”)异常,但此功能更适用于异常转换。有些人更喜欢Boost.Exception,作为在运行时可扩展的异常的另一种解决方案。

不要像干杯和赫斯那样陷入“只是断言陷阱”。简单的例子:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen)
{
    assert( fromLen <= bufLen );
    std::copy(from, from + fromLen, buf);
}

assert本身没有任何问题,但如果代码是为了发布而编译的(设置为NDEBUG),则safe_copy根本不安全,结果可能是缓冲区溢出,可能允许恶意方接管该进程。如前所述,抛出异常以指示逻辑错误有其自身的问题,但至少它会阻止发布版本中的直接未定义行为。因此,在安全关键功能中,我建议在调试中使用断言,在发布版本中使用异常:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen)
{
    assert( fromLen <= bufLen );
    if ( fromLen > bufLen )
        throw std::invalid_argument("safe_copy: fromLen greater than bufLen");
    std::copy(from, from + fromLen, buf);
}

当然,如果你经常使用这个模式,你可能希望定义一个自己的宏来简化任务。但是,这超出了当前主题的范围。

答案 3 :(得分:1)

抛出异常而不是断言的另外两个原因是,当您实现库或某种形式的可导出代码时,无法告诉 apriori 用户将如何处理某种形式的错误或何时您需要检查用户何时在RELEASE模式下构建代码(用户经常这样做)。请注意,在RELEASE模式下构建&#34;会消除&#34;任何断言。

例如,看看这段代码:

struct Node 
{ 
    int data; 
    Node* next;
    Node(int d) : data(d), next(nullptr) {}
};

// some code 
Node* n =  new Node(5);
assert(n && "Nodes can't be null");

// use n

当此代码在RELEASE模式下构建时,该断言&#34;不存在&#34;并且调用者可能会在运行时n获得nullptr

如果代码抛出了异常而不是断言,那么调用者仍然可以做出反应&#34;调试和发布版本中的nullptr异常。缺点是异常方法需要更多的锅炉板代码。