C++ 区分同一类型的多个异常

C++ 区分同一类型的多个异常,c++,c++11,exception,exception-handling,C++,C++11,Exception,Exception Handling,我不能完全理解用户将如何区分我的函数可能抛出的异常。我的一个函数可以抛出两个std::invalid_参数的实例 例如,在构造函数中: #include <stdexcept> // std::invalid_argument #include <string> class Foo { public: void Foo(int hour, int minute) :h(hour), m(minute) { if(hour <

我不能完全理解用户将如何区分我的函数可能抛出的异常。我的一个函数可以抛出两个
std::invalid_参数的实例

例如,在构造函数中:

#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("..."));
    }
}
然后,抛出
InvalidHour
InvalidMinute

编辑:为每个可能的异常创建一个类对我来说似乎有点过分,尤其是在大型程序中。是否每一个以这种方式有效使用异常的程序都有关于捕获什么的大量文档

正如在回答中所提到的,我已经考虑过断言。环顾stackoverflow,我发现大多数人都认为应该抛出一个异常(因为我的特例是针对构造函数的)


在浏览了大量关于何时使用异常的在线信息之后,普遍的共识是对逻辑错误使用
assert
,对运行时错误使用异常。尽管如此,使用无效参数调用
foo(int,int)
可能是运行时错误。这就是我想要解决的问题。

标准异常层次结构不适用于逻辑错误。使用
断言
并完成它。如果您确实希望将硬bug转换为更难检测的运行时错误,那么请注意,处理程序只能做两件合理的事情:以可能不同的方式实现契约目标(可能只是重试操作),或者反过来抛出异常(通常只是重试),而最初例外的确切原因在这方面很少起到任何作用。最后,如果您确实想支持代码,它真正尝试各种参数的组合,直到找到一个不抛出的参数为止,不管现在用文字写出来多么愚蠢,好吧,您有
std::system\u error
来传递整数错误代码,但是您可以定义派生的异常类

尽管如此,还是使用
断言


这就是它的用途。

您还可以创建更多的错误类,这些类派生自无效的\u参数,这将使它们可以区分,但这不是一个可扩展的解决方案。如果您真正想要的是向suer显示一条他可以理解的消息,那么无效的_参数的字符串参数就可以达到这个目的

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

不要像欢呼和欢呼一样落入“只是断言陷阱”。简单的例子:

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

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

引发异常而不是断言的另外两个原因是,当您正在实现库或某种形式的可导出代码,并且无法告诉apriori用户希望如何处理某种形式的错误,或者当用户以发布模式构建代码时需要检查时(用户经常这样做)。请注意,在发布模式下构建会“带走”任何断言

例如,请查看以下代码:

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
当此代码在发布模式下生成时,该断言“不存在”,调用方在运行时可能会得到
n
being
nullptr


如果代码抛出异常而不是断言,调用方仍然可以“作出反应”在调试和发布版本中都会出现
nullptr
异常。缺点是异常方法需要更多的锅炉板代码。

理想情况下,您应该有两种类型,
Hour
Minute
,它们在使用无效值构建时会抛出自己的异常类型。是的,您是正确的,您应该引入如果您希望以简洁的方式区分新类型,“用户将如何区分我的函数可能抛出的异常”为什么你需要用户/用户需要区分这两种情况?foo是否只验证它的参数?那不应该是
hour>23
?或者你想要25个不同的可能的小时吗?@fredwolflow:我认为在某些系统中,
24h00
是有效的(
24h30
不是)。因此,还有第三种情况,当两个参数的组合无效时。(很难找到正确/明确/简单的名称)。我曾经(像15年前)完成了您提供的解决方案,一个带有断言和异常抛出的组合,并在web上发布了一篇关于它的文章(今天这将是一篇博客文章)。为了稍微不那么不安全,一个传播到顶部的硬异常,取代
断言的异常不应该是标准异常。后来证明我没有使用该东西。其他人也没有使用。这就像用
宏定义
的想法一样(也是的BTDT).所以我认为你对我看似简单的建议的否定有点言之过早。这是基于你曾经发明的解决方案。嗯,在我这么做的时候,我最好注意到,不检查大量无符号整数参数的参数有效性检查,比不检查更糟糕。在不检查最常见的n错误(负值的总括)。如果您绝对想要64位版本的可能较大的64位范围,并且您想要一个描述性的名称,那么
using Size=ptrdiff\u t
是您的朋友。如果结果是运行时错误怎么办?在这种情况下,终止是不合理的,是吗?@shryasvinod:runtime erro
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);
}
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