Fog Creek Software
g
Discussion Board




How does one program w/o exceptions in C++/Java?

To those who dislike exceptions, how do you program without them?

Many agree that mixing return codes and exceptions is bad, but the standard libraries in C++ and Java both throw exceptions. Java does all over the place, and in C++ the new operator and the dynamic cast both throw exceptions.

Sorry to start yet another thread on the exception business, but I've not noticed this addressed anywhere.

Thoughts?

Mike Swieton
Thursday, October 16, 2003

The old way.

Either the routine returns something that can be used to tell if it succeeded or failed, or it sets a scope-local error variable that can be tested (usually, thread local; "errno" is the Unix/ANSI standard, and GetLastError() retrieves such a value on Windows).

e.g., the fopen() call either succeds, returning a valid FILE* pointer, or NULL, which indicates failure. If you want to know the nature of that failure, you check the errno variable (but more often than not, you don't care why something failed).

A complimentary technique is to give objects a "failure state", and make it impossible for most functions to fail. As an example, lets implement a write-only stream; it can be memory based, file based, or whatever. But it's write only and non seekable.

Then, I would guarantee that:
* Regardless of what's really happening, all calls are guaranteed to work in the sense that they won't dump core, throw exceptions, abort, or do anything.
* Once a call fails to do any real work, all other calls are guaranteed not to do any real work. (The streams transitions to "failure state"; there is no way to move out of "failure state")
* Before a successful "closing" of the stream, a pending error state must be acknowledged (or the program _will_ abort on assertion)
* It is always possible to query the failure state of an object.

Now, let's say I need to output some XML data. I'd write code such as:

Stream str;
str.write("<xml>");
str.write("<comment>");
str.write("blah");
str.write("</comment>")
SomeObject z;
char* y = get_xml_of_(z);
if (y) { str.wriite(y); delete y; }
str.write("</xml>")
z.uninitialize();
if (str.failed()) {
    /* handle failure */
    ...
    str.acknowledge_failure();
}
str.close()

Now, each write may internally fail for whatever reason (out of memory, out of disk space, inaccessible device, you name it). But whatever happens, my xml writers goes through the _same_ codepath, up until the latest failed() check, in which I handle the error at my own leisure.

There are exactly two codepaths to consider: Success and failure. If the different write() calls threw exceptions at the time of the underlying failure, I would have as many codepaths as I have write() statements, and I'd have to verify each to see that I properly dispose of 'y' and uninitialize 'z'. (RAII helps in this regard, but is not relevant in Java or .NET).

How is write() implemented? something along the lines of

stream::write(string x) {
  if (this->failed) return; /* do nothing in failure */
  if (x.length() != fwrite(x.buffer(), sizeof(char), x.length())
        this->failed = true;
}

That is, check the return value, and if it isn't what we expected, switch to failure mode (Real code would probably note down the present value of "errno" for later reporting of the failure reason).

Why do I insist on acknowledging failure? That's a "contract" - it's equivalent to throwing an exception if I know that something went unhandled. However, I don't want this exception to be caught. It's a bug that _can't_ be solved at a higher level in the call hierarchy. So I enforce this by not allowing to close a stream without acknowledging errors.

This technique is not applicable to all cases; E.g, if I was reading from a stream, and using the data I read, I'd need to check failure after every read. (There _are_ ways to apply this technique, but they're not as simple to describe).

Also, it might be inefficient - if I loop through 1,000,000 records, writing a few bytes for each, and fail on the first, I will still go through all the records when I could have stopped earlier. In most cases, the answer is "so what". It's an exceptional condition, and it _still_ takes much less than if it had succeeded (the actual writes are avoided). Obviously, if early detection is required, the loop can query the failure state and act accordingly (but this would introduce more code paths, which I try, in general, to avoid).

Finally, "failure" values are used by the CPU for math errors; When you divide by floating point zero in C, you get a NaN (Not a Number) - which is a failure-state number. Almost any operation you do with it will have a NaN result, indicating failure somewhere along the way. Thus, you can happily forget about the errors until you're ready to deal with them. Unfortunately, even though the general idea is the same, this is _not_ as simple as my first example, and you might escape from failure state along the way if you don't know what you're doing.

Ori Berger
Thursday, October 16, 2003

It's pretty easy to avoid exceptions in C++.  dynamic_cast only throws an exception when casting to a reference type.  Invalid dynamic_casts to pointer types simply return 0.  Use of dynamic_cast, in and of itself, is in no way required -- I've seen 100+ kloc code bases without any dynamic_casts.  The new operator throws an exception by default but this can be overridden and people commonly ignore 'out of memory' errors out of no hope of recovering anyway (not saying that's right, just common).

SomeBody
Thursday, October 16, 2003

Yes! The STL's running out of memory has seemed to me to be the only compelling reason for exceptions.

I program in C++ without handling exceptions, because I code 'real-time' server-side software, where I may take it as given that the machine is such that it isn't even using a swap file, let alone running out of memory.

I don't use dynamic_cast and, as Somebody said, dynamic_cast throws if it fails to cast a reference but not if it fails to cast a pointer.

Christopher Wells
Friday, October 17, 2003

Reading the two pervious posts, I obviously misunderstood the original question. Apologies to everyone who's time I may have wasted.

Ori Berger
Friday, October 17, 2003

The more interesting half of the question related to Java. Everybody knows that C++ exceptions were grafted on, and that the mix of the two models is sometimes maddening.

However, in Java, things are quite consistent. Exceptions are used all over the place. It would be impossible to create a program of even moderate complexity without at least some passing acknowledgement to the fact that exceptions exist in Java.

Brad Wilson (dotnetguy.techieswithcats.com)
Friday, October 17, 2003


Well, Joel's main complaint, I think, was that exceptions cause the program flow to jump around.

So while you can't literally use Java and not use exceptions (because all the libraries throw them), you can localize all the exception handling to the individual method calls. That is, handle all the exceptions that a method might throw immediately after the method is called. This simulates treating exceptions as return codes. This will look ugly, but it will avoid Joel's main complaint.

For example:
--------------------------------------

try {
  foo.method1();
} catch ExceptionOne {
  // error handling
} catch ExceptionTwo {
  // error handling
}

try {
  foo.method2();
} catch ExceptionTwo {
  // error handling
}

--------------------------------------
Instead of:
--------------------------------------

try {
  foo.method1();
  foo.method2();
} catch ExceptionOne {
  // error handling
} catch ExceptionTwo {
  // error handling
}

--------------------------------------

Bill Tomlinson
Friday, October 17, 2003

Well, let me ask this: do people use a lot of try/catch? Because our use of try/catch is outnumbered by try/finally by at least 10:1 (moreso if you include the "using" statement... we're a C# shop, but what holds true for Java here also holds true for C#).

We catch very few exceptions, and for very specific reasons. The only "global" catch handler is at the outer loop, so to speak, so that we can catch, log, and notify the user of some drastic error. This doesn't happen very often (and when it does, it almost always occasions one of those very specific try/catch blocks to be added).

Brad Wilson (dotnetguy.techieswithcats.com)
Friday, October 17, 2003

Pretty much the only time I use try { } catch { } is when I'm translating one exception type into another. Even then, I'm not really catching the exception, it's more like catch and release. ;-)

Chris Tavares
Friday, October 17, 2003

Brad, about not catching exceptions...

Aside from long arguments about how to handle errors, I find that a lot of times, at least in Java, exceptions are used as return codes. That is, to report un-exceptional errors.

As an example, if you are parsing a string (say one that you collected from the user in a free form text input field) and if the string isn't a number then Integer.parseInt() returns NumberFormatException. In most cases this is something that you would probably like to catch right there, give the user an error message and let the user change the field. Rather than doing anything fatal.

Now we can argue about whether using exceptions as return codes is a good idea or not (I think not, exceptions should be reserved for, well, exceptional events), but the point is that, yes, I do find that I need to catch exceptions from time to time.

Bill Tomlinson
Friday, October 17, 2003

Does anyone program w/o exceptions in Java?

Julian
Saturday, October 18, 2003

Bill,

I agree, and that's what I meant about catching specific exceptions in specific instances. But I find those instances to be pretty few and far between. Trying to open a file that may not be around... trying to parse input that may not be valid, etc. Certainly not the kind of stuff that ends up littering the code and making it "unreadable".

Brad Wilson (dotnetguy.techieswithcats.com)
Saturday, October 18, 2003

Brad,

I mostly agree, but I think that we're off topic.

In my first point in the thread I was just trying to answer the original post's question about using C++/Java "without using exceptions" by pointing out that you can treat exceptions like return codes by handling all of them immediately after the method call. This usage eliminates Joel's main complaint, which I took to be that exceptions move error handling away from the point of error in ways that Joel didn't like.

I was just making a theoretical point; not suggesting that using exceptions in that way was common, advocated, or necessary.

Bill Tomlinson
Monday, October 20, 2003

*  Recent Topics

*  Fog Creek Home