28

I am working on a project where we are restructuring old C code into new C++ where for error handling we are using exceptions.

We are creating different exception types for different modules.

I don't see it is worth but I don't have any valid argument to prove my point. So if we would have written the standard library, you would get to see vector_exception, list_exception and so on.

While thinking about it I stumbled upon this question:

When should you create your own exception type and when should you stick to the exceptions already created in std library?

Also, what could be the problems that we may face in the near future if we go by the above approach?

displayName
  • 13,888
  • 8
  • 60
  • 75
Amit Bhaira
  • 1,687
  • 6
  • 18
  • 31
  • 1
    Derive from exception and create custom exceptions; that is good for debugging because it helps the understanding of the code. Also, it gives a better overall structure – Alberto Miola Oct 01 '18 at 10:40
  • 1
    Idea is to have control. You catch the exceptions that your code rises, and you do only catches for those that you expect. This tells people who read your code which exceptions can happen here. If you catch some generic exception, it can come from anywhere, from your code, from dependencies, who knows, not the guy who looks at your code. If your reasoning not to do them is "it takes a full minute of my time", then rethink your reasoning. It's perfectly fine to derive them without adding *anything*, any functionality or the like. Just deriving them to be able to distinct them. – Aziuth Oct 01 '18 at 11:09
  • @Aziuth Then why there is a logical division of errors in std. And why only one bad_alloc. Why not list_bad_alloc, vector_bad_alloc. That is more descriptive. Isn't it? – Amit Bhaira Oct 01 '18 at 11:16
  • 2
    Be very judicious between expected errors versus exceptional errors. On my previous project, throwing an exception was equivalent to panic saving and killing the application. On another project, throwing an exception would fork and core-dump and then we'd do a post mortem stack backtrace to see what went awry. IMO, throwing exceptions ought not be used for flow control. – Eljay Oct 01 '18 at 12:28
  • 5
    @Eljay Depends on what you call control flow. Your two use-cases of exceptions are certainly extremes (though potentially justified if used consistently). There are lots of other use-cases for exceptions that don’t kill the application but rather are handled at runtime. – Konrad Rudolph Oct 01 '18 at 14:15
  • 1
    Duplicates abound. Some candidates: *[C++ exception class design](https://stackoverflow.com/questions/1335561)* and *[How to design exception “types” in C++](https://stackoverflow.com/questions/9387377)*. – Peter Mortensen Oct 01 '18 at 18:18
  • In my domain, we do not use exceptions. We use timestamped logs for traceability. An error may or may not be recoverable, and a log trail provides a sequence of events. The log messages are minimalist and timestamped. The book Patterns for Fault Tolerant Software by Robert Hanmer is a good place to start. – Xofo Oct 02 '18 at 03:30
  • @AmitBhaira The standard library is hardly the perfect example of how one should do everything, not sure why you want to orientate yourself on it that much. That said, differentiating between list bad alloc and vector bad alloc is not something I'd deem necessary. More important would be to differentiate between a list bad alloc in the standard library class and list bad alloc in your own list class. – Aziuth Oct 02 '18 at 08:45

6 Answers6

25

Create your own exception types when:

  1. you might want to differentiate them when handling. If they're different types, you have the option to write different catch clauses. They should still have a common base so you can have common handling when that is appropriate
  2. you want to add some specific structured data you can actually use in the handler

Having separate list and vector exceptions doesn't seem worthwhile, unless there's something distinctively list-like or vectorish about them. Are you really going to have different catch handling depending on which type of container had an error?

Conversely it could make sense to have separate exception types for things that might be recoverable at runtime, versus things that are rolled-back but could be retried, versus things that are definitely fatal or indicate a bug.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Not *might*. When you *do* want to handle them differently. You can always come back and change a throw later. You "might want," in principle, to later handle an exception differently for every single time you throw. – jpmc26 Oct 02 '18 at 06:10
  • If I'm writing a library, I probably want to classify my exceptions independently of any individual client code. At least, I should think in advance about the distinction between `logic_error` and `runtime_error`, and possibly retry-able runtime errors, even if naive client code won't bother re-trying. – Useless Oct 02 '18 at 08:44
  • Perhaps my experience is different since I don't really program in C++, but in my experience, the cases in which a retry is a reasonable solution are exceedingly rare. Furthermore, the standard exception types are nearly always enough to distinguish between "logic error" (usually some kind of argument error) and "runtime error." Most code written for production systems wouldn't even throw "runtime error"s itself; they'd leave that to libraries that provide a higher level interface with the system that's being interacted with (e.g., file system, database). – jpmc26 Oct 02 '18 at 09:15
10

Using the same exception everywhere is easy. Especially when trying to catch that exception. Unfortunately, it opens the door for Pokemon exception handling. It brings the risk of catching exceptions you don't expect.

Using a dedicated exception for all the different modules adds several advantages:

  • A custom message for each case, making it more useful for reporting
  • Catch can capture only the intended exceptions, more easily crashes on unexpected cases (yes, thats an advantage over incorrect handling)
  • On crash, IDE can show the exception type, helping even more with debugging
JVApen
  • 11,008
  • 5
  • 31
  • 67
  • 3
    TIL I learned that Pokemon Exception Handling is a real term. If you need to look it up like me, a quick link that may [help](https://softwareengineering.stackexchange.com/questions/319088/is-indiscriminately-catching-exceptions-pokemon-exception-handling-ever-accept)... – RMSD Oct 01 '18 at 18:52
5

I think that in addition to the reasons in other answers could be code readability, because a lot of time programmers spend supporting it. Consider two pieces of code (assuming they throw "empty frame" error):

void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw std::exception("Error: empty frame");
    }
}
void MyClass::function() noexcept(false) {

    // ...
    if (errorCondition) {
        throw EmptyFrame();
    }
}

In the second case I think it is more readable, and the message for the user (which printed with what() function) is hidden inside this custom exception class.

stackoverflower
  • 545
  • 1
  • 5
  • 21
2

I see two possible reasons.

1) If you want to store in the exception class some custom information about the exception, then you need an exception with extra data members. Often you will inherit from one of the std exception classes.

2) If you want to differentiate between exceptions. For instance suppose you have two distinct invalid argument situations and in your catch block you want to be able to differeentiate between the two. You can create two child classes of std::invalid_argument

Fabio
  • 2,105
  • 16
  • 26
2

The issue I see with each element of the STL firing a distinct memory exception is that some parts of the stl are built on others, so queue would have to catch and translate list exceptions, or clients of queue would have to know to handle list exceptions.

I can see some logic in separating the exceptions from different modules, but I can also see the logic of having a project-wide exception base class. I can also see the logic of having exception type classes.

The unholy alliance would be to build an exception hierarchy:

std::exception
  EXProjectBase
    EXModuleBase
      EXModule1
      EXModule2
    EXClassBase
      EXClassMemory
      EXClassBadArg
      EXClassDisconnect
      EXClassTooSoon
      EXClassTooLate

Then insist that every actual fired exception is derived from BOTH a module and a classification. Then, you can catch whatever makes sense to the catcher, such as a disconnection, rather than Having to separately catch HighLevelDisconnect and LowLevelDisconnect.

On the other hand it is also completely fair to suggest that the HighLevel interface should have completely dealt with LowLevel failures, and they should never be seen by the HighLevel API client. This is where catching by module would be a useful last-ditch feature.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • 1
    Why do you call this an “unholy alliance”? It’s a fairly typical approach … (EXCept for the naming). – Konrad Rudolph Oct 01 '18 at 14:04
  • Fair enough @KonradRudolph . I have never really needed to do it, so I wouldn't know if it is "typical" to impose multiple inheritance and perhaps virtual inheritance on exceptions. I know they support it. – Gem Taylor Oct 01 '18 at 14:09
  • 1
    I completely missed the multiple inheritance. In that case I agree. Very unholy. Although I kind of dig the idea. – Konrad Rudolph Oct 01 '18 at 14:11
1

I would ask, of the try...catch section, "does this code know how to recover from the error, or not?

Code which uses try...catch is anticipating that an exception could happen. Reasons that you would write try...catch at all instead of letting the exception simply pass through are:

  1. Add some code to make the parent function exception safe. A lot of this should be done quietly with RAII in destructors, but sometimes (e.g. a move operation) need more work to roll-back a partially completed job, it depends on the level of exception safety you want.
  2. Emit or add some debugging information and rethrow the exception.
  3. Try to recover from the error.

For cases 1 and 2 you probably don't need to worry about user exception types, they will look like this

try {
    ....
} catch (const std::exception & e) {
    // print or tidy up or whatever
    throw;
} catch (...) {
    // print or tidy up or whatever
    // also complain that std::exception should have been thrown
    throw;
}

This will probably by 99% of real world cases, in my experience.

Sometimes you will want to recover from the error. Maybe the parent function knows that a library will fail in certain conditions which can be fixed dynamically, or there is a strategy for re-attempting a job using different mechanisms.

Sometimes the catch block will be written specifically because the lower-level code has been designed to throw exceptions as part of its normal flow. (I often do this when reading untrusted external data, where many things can predictably go wrong.)

In these, rarer cases, user-defined types make sense.

spraff
  • 32,570
  • 22
  • 121
  • 229