I'm trying to understand error handling in C++.
I have read that using try, throw, catch is better style and less complicated than using if statements with return values. But I'm not sure I really understand how try, throw, catch works. I made a simple example below and it would be great to get feedback about any problems or bad style. My goal is to make a function out of the example that checks the results of another calculation.
Here are questions I have about try, throw, catch: (1) Should the catch statement be included in my function? Or should it be somewhere else, like in main() or in the function where the initial calculation is done?
(2) Is it overkill to use try, catch, throw for something this simple (I would like to improve my style)?
(3) If there is an error, I would like to terminate the program. How would I do that? Or does "catch" mean that that is done automatically?
(4) I don't understand the use of cerr. Why not just use cout? Have I used cerr correctly here? Should I also have used it in the if/else statements?
Thanks a lot for any help.
Here's the example I made:
double calculated = 10.2; // from previous calculation
double tolerance = 0.3; // I can set this in this function
double valueWanted = 10.0; // from previous calculation
const int calcError = 5; // I picked this number randomly to use for indicating an error
try
{
if (fabs(fTargetValue - fCalculated) <= fTolerance)开发者_如何转开发
cout << "Result is within range.";
else
cout << "Failed.";
throw calcError;
}
catch (const int calcError)
{
cerr << "The calculation failed.\n" << endl;
}
Well that's a lot of questions. I will try to give you some hints :
(1) Do not include the try-catch in your function. Throwing an exception is done to tell the outer world that something happened. If you can handle the problem in your function, do not throw at all ^^ A good error handling is generally to catch the error ASAP (in the caller) or in a general purpose handler far away like in main, to handle gracefully unhandled errors.
(2) As a rule of thumb, use exception for ... exceptional things. Errors are good candidate for exceptional things. An exception could be thrown for things like overflow or division by zero in a math library. You have to decide, but in general it is good to handle errors with exceptions.
(3) catch do not mean that your program will end. In fact it is the contrary. By catching an exception, you say that you will handle the problem ^^ If you want to terminate, a simple way in a simple program is to not catch exception, as the default behavior for uncaught exception is program termination ^^ Instead, you can explicitly terminate your program in some catch block.
(4) cerr is just like cout, but is a different file descriptor. It means that external programs can differentiate cerr from cout. It is used for error, but that's not really important but for external programs.
my2c
Ok, firstly your example will throw every time because you do not have scope braces after the else
. Therefore, only cout << "Failed.";
will be executed and throw calcError
will be executed each time, regardless of whether the result was in range or not. Change this to:
else
{
cout << "Failed.";
throw calcError;
}
In the event that an exception is throw, the code will begin within the catch
block you have defined, stating the calculation failed.
If the result was in range (throw
is never called), code will begin executed directly after your catch block.
When you throw a type, that type arrives at the catch handler. This allows you to define catch handlers for different types. In this case, you are throwing (and catching) a const int
. That's all good. Generally, we throw std::exception
or a derivation of this. Your own exception classes can contain information pertinent to the error. In your case you could include a simple message that it was out of range, or indeed include the const int that failed.
The catch statement should be in the first function up from the one that throws (maybe in the function that throws) that can recover from the exception and allow the program to continue normally.
Yes, there's no point throwing really if you expect to be catching it. Also, your normal program flow shouldn't throw. As a rule of thumb, only throw for when you get into a situation you don't really expect should ever happen. Exceptions are called exceptions because they happen in exceptional circumstances. Often a good time to use exceptions is when interacting with the programs environment. You usually expect certain things to work, eg to be able to allocate memory, open a file, receive a complete data packet from a network device etc. All these cases should result in an exception being thrown. Also, if your program receives input, it should initially validate it. But, later on, during processing, if there's something wrong with the data that should already have been rejected by the validation, such as a divide by zero occurring because of strange input data, that would also be an exceptional situation. IF you rely on exceptions too much for when expected things happen, the flow and logic of your program can become overly difficult to reason about and the program maintenance gets unnecessarily hard.
If there is an error, just don't catch. If there's no catch, the exception will go all the way up to your main function, and will then go to the runtime from there which will terminate your program. And, for some O.S.s such as windows this will cause a minidump file to be created which you could use to debug your program with to find out what exception caused it to terminate.
cerr and cout just give you two ways of outputting information from your program. In cases where another program consumes the output of your program to do something, it will read cout and expect to understand it. This means if you want to write out errors or warning that the consuming program won't understand you have to write them to cerr so that you don't confuse the second program that's reading your programs normal cout output.
The C++ standard has a few exception classes that are all derivable from. I suggest you do that instead of throwing and catching PODs. It's not hard, either, and would improve (and specify the type of error) like so
class CalculationError : std::invalid_argument
{
public:
CalculationError(std::string const& msg)
: std::invalid_argument(msg)
{}
};
For a quick overview of the exception hierarchy go to http://www.richelbilderbeek.nl/CppExceptionHierarchy.htm
The problem is: when you throw a POD type, there is no attached message. The great part about throwing exceptions is the ability to write out a message about what might have gone wrong, and how to fix it. This is not possible when throwing an int.
There are three output streams in C++: log, cerr, and cout. Each of them are represented differently, which means that, when starting your program, you can use the command line to filter out each of these streams. This is great for debugging then, as you can filter by cerr and see if your program failed a test.
Example: my_program > out.txt 2> log.txt
(cout goes to out.txt, the others to log.txt)
However, I would recommend not just cerring. Usually, the point of a catch is to reverse program state! If you, for example, tried to allocate a dynamic array, and that fails, the catch would be responsible for destructing the array again before rethrowing. Otherwise, you would have a whole bunch of things like memory leaks and whatnot.
What's also important to note is that, once caught, the exception is "swallowed". If you cannot or do not want to deal with the error here, it is best to write
catch(/* Error to be caught */)
{
throw; // Rethrows original exception, propagating it upwards
}
If you want some good literature about this, Herb Sutter wrote a book called Exceptional C++
and it covers exception safety in a practical and enlightening way (imo). It's definitely worth checking out if you want to know when and why you need to throw exceptions.
Hope this helps!
Did not you forget a block around the else case
try
{
if (fabs(fTargetValue - fCalculated) <= fTolerance)
cout << "Result is within range.";
else {
cout << "Failed.";
throw calcError;
}
}
There are lots of good answers here for your questions. I was just reading about cerr and I would like to share what I was reading here. The source is "C++ Basic Structures: Vectors, Pointers, Strings, and Files" course in Coursera platform.
Here is the quote: "When printing error messages, cerr is preferred over cout. cerr is not bufferred, which means it is not stored in memory to be printed later on. It just gets printed immediately. Therefore, as a rule of thumb, important data and variables should be printed with cout while error messages should be printed with cerr."
Also: "cerr is an unbuffered output stream. This means that the output is immediately printed and not stored for later. This makes cerr more suitable for use with error messages where its storage is not important.
On the other hand, cout is buffered, meaning its output is temporary stored so that the system can retrieve it later for quicker access. cout is more suitable for important data and variables that are used throughout the program."
精彩评论