Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distinguishing between multiple exceptions of the same type

I can't quite wrap my head around how a user will be able to distinguish between the exceptions my functions can throw. One of my functions can throw two instances of std::invalid_argument.

For example, in a constructor:

#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("..."));
    }
}

Note: It's an example, please do not answer with bounded integers.

Say the user calls with foo(23, 62);, how would the user's exception handler distinguish between the two possible instances of std::invalid_argument?

Or am I doing this wrong, and I should inherit from std::invalid_argument to distinguish between them? That is,

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

Then, throw InvalidHour and InvalidMinute?

Edit: Creating a class for every possible exception seems a little too much to me, especially in a large program. Does every program that effectively uses exceptions this way come with extensive documentation on what to catch?

As mentioned in an answer, I have considered assert to. Looking around stackoverflow, I have found a majority of people saying you should throw an exception (as my particular case is for a constructor).

After looking around a lot of online information on when to use exceptions, the general consensus is to use an assert for logic errors and exceptions for runtime errors. Although, calling foo(int, int) with invalid arguments could be a runtime error. This is what I want to address.

like image 382
Shreyas Avatar asked Aug 04 '15 08:08

Shreyas


People also ask

How do you handle multiple exceptions?

By handling multiple exceptions, a program can respond to different exceptions without terminating it. In Python, try-except blocks can be used to catch and respond to one or multiple exceptions. In cases where a process raises more than one possible exception, they can all be handled using a single except clause.

How do you catch multiple exceptions in a single catch?

Java allows you to catch multiple type exceptions in a single catch block. It was introduced in Java 7 and helps to optimize code. You can use vertical bar (|) to separate multiple exceptions in catch block.

Can you throw multiple exceptions in one throw statement?

You can't throw two exceptions. I.e. you can't do something like: try { throw new IllegalArgumentException(), new NullPointerException(); } catch (IllegalArgumentException iae) { // ... } catch (NullPointerException npe) { // ... }

Can we handle multiple exceptions in single catch block?

Handling More Than One Type of Exception The catch clause specifies the types of exceptions that the block can handle, and each exception type is separated with a vertical bar ( | ). Note: If a catch block handles more than one exception type, then the catch parameter is implicitly final .


3 Answers

The standard exception hierarchy is unsuitable for logic errors. Use an assert and be done with it. If you absolutely do want to transform hard bugs into harder to detect run time errors, then note that there are only two reasonable things a handler can do: achieve the contractual goal in some possibly different way (possibly just retrying the operation), or in turn throw an exception (usually just rethrowing), and that the exact cause of the original exception seldom plays any rôle in this. Finally, if you do want to support code that really tries various combinations of arguments until it finds one that doesn't throw, no matter how silly that appears now that it's written out in words, well, you have std::system_error for passing an integer error code up, but you can define derived exception classes.

All that said, go for the assert.

That's what it's for.

like image 53
Cheers and hth. - Alf Avatar answered Oct 27 '22 01:10

Cheers and hth. - Alf


You could also create further error classes that derive from invalid_argument, and that would make them distinguishable, but this is not a solution that scales. If what you actually want is to show the suer a message that he can understand, then the string parameter to the invalid_argument would serve that purpose.

like image 45
A Hernandez Avatar answered Oct 27 '22 01:10

A Hernandez


The standard exceptions do not allow storing the additional information you want, and parsing exception messages is a bad idea. One solution is to subclass, as you mention. There are others - with the advent of std::exception_ptr. it is possible to use "inner" (or "nested") exceptions as in Java or .NET, though this feature is more applicable to exception translation. Some prefer Boost.Exception, as another solution for exceptions extensible at runtime.

Don't fall into the "just assert trap" like Cheers and hth. Simple example:

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

There's nothing wrong with the assert per se, but if the code is compiled for release (with NDEBUG set), then safe_copy will not be safe at all, and the result may be a buffer overrun, potentially allowing a malicious party to take over the process. Throwing an exception to indicate a logic error has its own problems, as mentioned, but at least it will prevent the immediate undefined behavior in the release build. I'd therefore suggest, in security-critical functions, to use assertions in the debug, and exceptions in the release build:

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

Of course, if you use this pattern a lot, you may wish to define a macro of your own to simplify the task. This is beyond the scope of the current topic, however.

like image 35
Arne Vogel Avatar answered Oct 26 '22 23:10

Arne Vogel