Re: I keep running into long term c++ programmers who refuse to use exceptions
WalterHoward@gmail.com wrote:
Disease: Return codes corrupt the meaning of the word "function" and
destroy the code's usability as a function
Mathematically speaking, the definition of a function is this: http
http://en.wikipedia.org/wiki/Function_%28mathematics%29. It's
important to note that a function translates multiple inputs into
exactly one output.
It doesn't mean very much to say "exactly one output", since that
"exactly one output" can also be one object containing an arbitrary
number of subobjects. For example, std::set<T>::insert(T) returns a
std::pair<std::set<T>::iterator, bool>.
Disease: Functions tangled with return code checking, propagate their
problems to anyone else using them, spreading complexity like a
cancer.
[...] The iostream library, bless
its heart, was written before exceptions were part of the language. I
suggest if you use it, wrapping it in your own classes that do throw
exceptions if failures occur.
No need to write your own classes; just call
std::basic_ios<...>::exceptions(std::ios_base::iostate).
Disease: Error handling code doesn't get tested.
[...]
What you can do is go through your code and replace catch() with a
macro CATCH()
try
{
socket.Connect("127.0.0.1:80");
}
CATCH(const Exception& ex)
{
delete socket;
Log("Could not connect to %s");
}
Now, define CATCH(X) catch(X) {};
So now your code expands to this:
try
{
socket.Connect("127.0.0.1:80");
}
catch (const Exception& ex)
{
}
{
delete socket;
Log("Could not connect to %s");
}
Which makes the error handling code ALWAYS EXECUTE. Now you run your
code. The catch block you wrote to handle the error ALWAYS gets called
and tested. When you've tested it, you remove the CATCH macro on that
single piece of code and run the program again exercising the NEXT
catch block. Keep doing this until all your error handling code (catch
blocks) is tested.
This works only for the last handler in any given handler sequence;
i.e. it breaks when there is another subsequent handler.
Why don't you just insert a throw expression in the try block?
It doesn't have such a limitation (or bug), is simpler, and can even
be configured run-time without recompiling; for example:
try {
// ...
if (TestingSomeException)
throw SomeException(...);
// ...
}
catch (const SomeException& e) {
// ...
}
catch (const AnotherException& e) {
// ...
}
(Don't use preprocessor for what other techniques can work better.)
By the way, it's strange that you're not using a smart pointer and RAII
in the example, while advocating the exceptions so much. :)
Here is a rich but simple exception class that I recommend you start
using as soon as possible.
[...]
class Exception: public std::exception
{
int SystemError;
int ApplicationError;
std::string Location;
std::string Message;
mutable std::string What; // Necessary kludge to deal
with std::exception being lame and returning const char* for what()
You probably mean it's because what() is a const member function,
not because what() returns const char*. (In fact, with the definition
of what() you give below, What doesn't have to be mutable.)
public:
Exception(const std::string& message, const std::string&
location = "", int systemError = 0, int applicationError = 0)
: Message(message), Location(location),
SystemError(systemError), ApplicationError(applicationError)
{
// If the caller doesn't set the error,
derive it from the last error the system is aware of
if (systemError == 0)
systemError = errno;
}
You access errno only after constructing several members, but its value
could have been changed by library function calls in the middle.
It should be checked (including being saved to another variable) right
after the call reporting the error but before any other subsequent calls.
const char* what()
{
char errorString[1024] = "Unknown";
// If strerror fails, errorString will
remain set to "Unknown"
strncpy(errorString,
strerror(SystemError), sizeof(errorString));
The definition of strerror in the standard (ISO/IEC 9899:1999 7.21.6.2)
seems to imply that it can never "fail":
p2: [...] strerror shall map any value of type int to a message.
p4: The strerror function returns a pointer to the string [...].
What = "What: " + Message + "\n" +
// Only display system error system if
there was one (non-zero)
(SystemError > 0 ? std::string("Why: ") +
errorString + "\n" : "") +
// Only display location if we had set it
in the constructor
(Location.empty() ? "" :
std::string("Where: ") + Location + "\n");
return What.c_str();
}
Wow, how many operator+'s and how many temporaries do we get here? :)
It's weird that you keep What as a member but compute its value in
every call to what(); you could have cached it in the first call and
skipped the computation in later calls if you wanted a lazy evaluation,
or computed it once early in the constructor (and even make What non-
mutable).
I do not know the general consensus on the usage of what(), but many
people hold that it doesn't have to be the final string to be logged or
shown to the user but a simple key or index to a message in a catalog.
My feeling is that otherwise the exception class itself could need to
do too much work (such as formatting and localization) or could be too
inflexible.
I'm also worried that this function or this class may be too heavy.
Now that you have a good exception class, how do you use it?
The most important thing about using exceptions is to be very clear
about what an exception is. If you want your code to make the jump to
light speed you can't go halfway with exceptions. A lot of programmers
are stuck between the old and new paradigms. I hear this quite often
and it's a problem, "Exceptions are for fatal errors but I still use
return codes for some things". This is not going to work nearly as
well. A very simple and clear criteria for using exceptions is, throw
an exception when a function fails to perform its function. This
means, no more error code returns at all, none, nada, zilch.
That may be a nice world to live in, but we're in a world that already
carries the burden of backward compatibility; we cannot get away with
well-established and widely-used APIs that follow the "old" paradigm.
Following the "new" paradigm may be possible when all those "old" stuff
are wrapped nicely in a modern C++ fashion, but I find myself still
directly using the Berkeley socket interface even in C++...
Bad Use: catch(?)
[...]
One solution is to catch(?) in shipping code so your users see fewer
of your warts and perhaps the program can limp along still functional,
however, during development you never want to suppress errors. Using a
macro for catch(?) will do the trick:
#ifdef _DEBUG
#define CATCHALL catch(const Exception& e)
#else
#define CATCHALL catch(?)
#endif
Then what do you do in the catch block? I cannot think of anything
significantly more than an empty block or a mere writing of "something
bad happened". You cannot use the caught exception e even in the debug
build; discarding the information by using such a 'unified' catch block
doesn't look like a good idea.
--
Seungbeom Kim
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]