We know that the exception class has two derived classes: logic_error and runtime_error.
logic_error has four derived classes: domain_error, invalid_argument, length_error and out_of_range.
runtime_error has three derived classes: range_error, overflow_error and underflow_error.
While some of them are self-explanatory, like overflow_error and underflow_error, some are not that clear, especially range_error, both MSDN and cplusplus just say "to report a range error", which is close to saying nothing, how it is different out_of_range and domain_error???
Another question is when I throw an exception, which one should I choose? For example, In reverse_string(char* s), which exception to throw when s is NULL? In float calc_ellipse_area(float a, float b), which to throw when a or b is <=0? Which to throw when a == b (strictly speaking, circle is not an ellipse!)?
Finally, practically speaking, does it really matter if I throw an exception which is not correctly categorized?
Exception handling in C++ catches things by matching on the exception's type, it doesn't catch by matching on an exception's value, so it makes sense to throw things of different types to allow fine-grained handling.
Exceptions are as old as programming itself. An unhandled exception may cause unexpected behavior, and results can be spectacular. Over time, these errors have contributed to the impression that exceptions are bad. But exceptions are a fundamental element of modern programming.
A logic error is the (theoretically) result of a programmer error. A runtime error is something that could not easily have been prevented by the programmer.
When you write a function, it is useful to document its preconditions and/or assumptions. If those preconditions are broken, it is a logic error.
A runtime error is usually due to something external: a file operation failed, a printer is offline, a DLL could not be loaded.
If a path parameter is malformed, that is a logic error. If it is a valid path string, but does not exist, or you don't have permission to access it, that is a runtime error.
Sometimes it becomes arbitrary. Bad user input is probably a runtime error, but failure to validate user input is more of a logic error. It may not be obvious which is the case in a particular situation.
If something starts in 01 Feb 2011, a finish date of "01 Jan 2011" is an invalid argument, while "31 Feb 2011" is out of range. A finish date of "fish and chips" is a domain error. Length errors are often about buffer size, but that might also include too much or too little input data or something like that.
A range error is similar to an out of range error, except for the context (runtime, not logic). e.g. Number of printers available = 0. Overflow and underflow are more or less self-explanatory.
In the end, use them in a way that you and your colleagues find meaningful -- or possibly don't use them at all. Some people just use std::exception for everything.
It is only really important if you are going to handle different exceptions differently. If you are just going to display (or log) a message and carry on, it doesn't matter what exception(s) you use.
For example, In reverse_string(char* s), which exception to throw when s is NULL?
In float calc_ellipse_area(float a, float b), which to throw when a or b is <=0? Which to throw when a == b (strictly speaking, circle is not an ellipse!)?
For both of these, use std::invalid_argument
.
Or you can define your own exception called null_argument
deriving from std::logic_error
(or from std::invalid_argument
), and use it for NULL
argument.
The point is that if none of the standard exception class applies to your situation, or you want more specific exception, then define one deriving from existing classes.
For example, if you want to throw exception when you encounter an invalid index, then either you can use std::out_of_range
or define more specific class called index_out_of_range
deriving from std::out_of_range
.
does it really matter if I throw an exception which is not correctly categorized?
Yes, it does matter. For example, it increases readability of your code. If you throw std::logic_error
when an invalid index is encountered, then well, it doesn't add much to the readability, but instead of that if you throw std::out_of_range
, then it greatly increases the readability. And if you throw index_out_of_range
, it increases even more, as it's more specific.
From the standard:
- The Standard C++ library provides classes to be used to report certain errors (17.6.5.12) in C++ programs. In the error model reflected in these classes, errors are divided into two broad categories: logic errors and runtime errors.
- The distinguishing characteristic of logic errors is that they are due to errors in the internal logic of the program. In theory, they are preventable.
- By contrast, runtime errors are due to events beyond the scope of the program. They cannot be easily predicted in advance.
However, all the exception types derived from runtime_error
are misclassified -- all of them are easily preventable.
For actual runtime errors, the C++ standard library is rather inconsistent, it sometimes uses return values or internal state (e.g. iostream::bad()
. And when it does use exceptions, they don't derive from runtime_error
. For example, std::bad_alloc
is a direct subclass of std::exception
.
In conclusion, you shouldn't ever use std::runtime_error
or any of its predefined subclasses.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With