Re: try, finally

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Mar 20, 2008 at 8:23 AM, John Love-Jensen <eljay@xxxxxxxxx> wrote:
>  In my projects, throwing an exception mean "the application is about to
>  terminate."
>
>  That's using the exception mechanism at the extreme of conservative
>  programming with exceptions.
>
>  Assuming you use exceptions with less draconian policy, the exception
>  mechanism is not for using as normal flow control.  It really means an
>  exceptional situation, outside of the normal flow control.
>
>  For normal flow control -- such as handling predictable, common error
>  conditions -- you should use return codes.

It seems that the root of any disagreement is what kinds of errors
we'd prefer to represent with exceptions. You and Ted would use them
for rare, fatal error conditions, similar in spirit to machine
exceptions such as access violations and invalid instructions. I would
use them for more common errors such as invalid user input, missing
files, network errors, etc. I have a hunch nobody is going to be
having a change of heart any time soon. :-) One thing that I have
often noticed, incidentally, is that the longer a programmer has been
programming before C++, the more they prefer error codes to
exceptions.

One thing I can do, however, is present a strong example of benefits
of using exceptions for more common errors rather than error return
codes. I can think of many such examples but I'll try to construct one
good one. I will warn you ahead of time, this will be a long email
(think of it more as an article, and feel free to comment). :-)

The first example is fairly simple. It becomes more pronounced in
larger applications. Let's say you have a base class that defines an
interface to something, and many derived classes that implement that.
This is a well-known and common situation. Here the example can be a
base interface that describes how to get, say, an image from some
source (Image is an object that can hold an image). For simplicity's
sake we'll say that derived constructors and destructors should not
cause errors, and that all real work should be done by GetImage():

class CImageSource {
public:
  CImageSource ();
  virtual ~CImageSource ();
  virtual void GetImage (Image &img) = 0;
};

Let's say we have 30 different image sources, but here are two of
them. First, using exceptions to represent error conditions:

// This is the base exception we'll be using. It contains a lot of info.
class EBaseException {
   // let these even have a function to display a message box:
   void GUINotify (...); // perhaps this takes some handle to current
window, etc.
   // ... interfaces to private data left out of example ...
private:
   string sourcefile_;
   int sourceline_;
   string message_;
   Severity severity_; // fatal? warning? etc.
   StackTrace trace_; // hypothetical stack trace for sending dump to author.
   // etc...
};

// Provides an image from an HTTP resource.
class CHTTPImageSource {
public:
  void GetImage (Image &) throw (EBaseException)
};

// Provides an image from the FOO-1200 high performance xray vision
// camera, which has a custom set of drivers for talking to it. The
// author of this code assumes no responsibility for misuse of this
// device. ;-)
class CFOO1200ImageSource {
public:
  void GetImage (Image &) throw (EBaseException)
};


You should be getting the idea at this point. Let's say that our
program has a GUI on it. The user has configured the image source
elsewhere. There is a button on the GUI that the user can press to
grab an image from the image source. Pressing that button should
display the image, notifying the user if an error occurs.  Let's say
pressing that button calls OnButton(), which calls
GrabAndDisplayImage() to display the image, again using exceptions:

// In this example ENullPointerException derives from EBaseException, and
// display_ is some thing that displays images. I've left these out. I have also
// used ... in the exception constructor to represent the other things you may
// pass to the hypothetical constructor, __FILE__, __LINE__, whatever.
void UI::GrabAndDisplayImage (CImageSource *source)
  throw (Exception)
{

    Image img;

    if (!source)
      throw ENullPointerException(..., "No image source specified.", ...);

    source->GetImage(img);
    display_->DisplayImage(img);

}

void UI::OnButton () {

  try {
    GrabAndDisplayImage(source_);
  } catch (Exception &x) {
    x.GUINotify();
  }

}


And that's it for the higher level part. The various implementations
of CImageSource throw exceptions containing information about errors
specific to those types of devices. Each CImageSource may even even
define it's own EBaseException subclasses that it can use, the rest of
the application need not be aware of these types. The use of 'image'
in GrabAndDisplayImage() provides for automatic cleanup if any
exceptions are thrown. The button on the GUI does what it needs to do:
grabs and displays the image, notifying the user on error. It is
assumed that the error messages are well-constructed enough to contain
recovery instructions. The code is also exceptionally (pun intended)
clean. There is no duplicated cleanup code, no translation of error
codes from one form to another, no querying of error strings, no
hard-to-read branching. On top of that, every error may have a wealth
of other information associated with it -- information that you could
send to the author as a bug report (functionality that could be taken
care of by EBaseException::GUINotify()), information about the
severity of the error. Logging can be done in the EBaseException
constructor to provide automatic and complete error logging without
modifying any application code. Everything just works. Also the
possibilities are endless. You can take advantage of the exception
hierarchy to provide specific recovery options:

try {
  GrabAndDisplayImage(source_);
} catch (EConfigurationError &x) {
  x.GUINotify();
  DisplayConfigurationGUI();
} catch (EBaseException &b) {
  b.GUINotify();
}

Here, for example, any exception thrown that has derived from
EConfigurationError will cause the code to display a configuration
settings dialog to the user. This provides you with a way to handle
entire classes of errors in similar ways, without caring about the
individual details, and on a per-function basis (for example, in a
batch processing scenario you may just handle EBaseException and not
treat EConfigurationError specially).

I have even seen applications designed where exceptions know a little
bit about how to recover from an error. For example:

class EBaseException {
public:
  virtual void Recover () { }
};

class EConfigurationError : public EBaseException {
public:
  virtual void Recover () { DisplayConfigurationGUI(); }
};

And so on and so forth. That is the magic of exceptions.

Now, let's say we do not want to use exceptions. Instead we are going
to use error codes here. Everything that was done with exceptions
above is also entirely possible using error codes. It's not a matter
of what you can and can't do. However, a lot of extra coding will be
required to emulate all the features of exceptions using error codes.
So, revisit the CImageSource, a la error codes:

class CImageSource {
public:
  CImageSource ();
  virtual ~CImageSource ();
  virtual int GetImage (Image &img) = 0;
};

Here, GetImage() will return an integer error code. The first thing
you may notice is there are no messages inherently associated with
error returns. You can now take two approaches, both with issues. The
first approach:

// Error codes; or you could use const int, enum, whatever you prefer:
#define IMGSRCERR_OK  0
#define IMGSRCERR_NULL_POINTER  1
// Etc...

class CImageSource {
  ...
  // given an error code, return a string
  static string ErrorStr (int errcode);
  ...
};

A few things you should immediately notice. First of all, in order for
ErrorStr() to work properly, all error codes must be unique.
Therefore, either individual subclasses must be coded with awareness
of the other error values in mind (so no conflicts are created for
device-specific error codes), or, alternatively, some very general
broad set of common errors must be defined that any device-specific
errors would fall into (for example, IMGSRCERR_NOTREADY if an image
source device was "not ready", which doesn't necessarily apply to all
sources anyway). The issue with the former is maintenance, although if
you can keep it up, then it's a reasonable solution. The issue with
the latter is vagueness. One common solution I have seen to these
problems is to use, say, 32-bit error codes where the high 16-bits are
some sort of general "class" and the low 16-bits are a specific error
code. In any case, to avoid the vagueness, the implementation of
ErrorStr must be aware of all of the subclasses and their error codes.
This has already complicated things over using exceptions, where this
requirement does not exist.

I mentioned that you could take two approaches to associating strings
with error codes. The second approach is this:

class CImageSource {
  ...
  // Given a source-specific, return a string.
  virtual string ErrorStr (int errcode);
  ...
};

In that approach, each subclass is responsible for defining it's own
error codes and strings. The base implementation could return a
generic message, as well, so unknown codes could be deferred to the
base:

string CHTTPImageSource::ErrorStr (int errcode) {
  string msg;
  if (errcode is known)
    msg = the message;
  else // let base handle unknown/commong messages
    msg = CImageSource::ErrorStr(errcode);
  return msg;
}

This solves most of the problems above, but introduces a different
collision problem where different error codes may mean entirely
different things for different devices. Again, unless each subclass is
aware of the other subclass's error codes, you will have issues. An
example of the issues here is a function that grabs an image from two
sources and does something with them, where you want the error
handling mechanism to be defined by the caller. First with the error
code interface above:

int HandleImages (CImageSource *a, CImageSource *b) {

  Image x, y;
  int err;

  err = a->GrabImage(x);
  if (err != IMGSRCERR_OK)
    return err;

  err = b->GrabImage(y);
  if (err != IMGSRCERR_OK)
    return err;

  DoStuff(x, y);

  return IMGSRCERR_OK;

}

And now if the caller wants to display a message box, well... using
the static ErrorStr as above it could do this:

  int err = HandleImages(a_, b_);
  if (err != IMGSRCERR_OK)
    DisplayError(CImageSource::ErrorStr(err));

If you chose the virtual ErrorStr() interface above, you're outta
luck; you either have to return the error string itself from
HandleImages() or somehow pass information back about which caused the
error.

Using exceptions, of course:

void HandleImages (CImageSource *a, CImageSource *b) {

  Image x, y;

  a->GrabImage(x);
  b->GrabImage(y);

  DoStuff();

}

And to take appropriate action:

  try {
    HandleImages(a_, b_);
  } catch (Exception &x) {
    x.GUINotify();
  }

Using exceptions, every problem above has been avoided, and the code
has a much higher ratio of work to error-checking as well.

It actually gets worse than that in this example. One thing that you
may have noticed above is that no checks for errors in DoStuff() are
performed. Using error codes, your HandleImages() function must check
the return value of DoStuff() and pass it back to the caller. This now
multiplies the set of problems described above:

 - HandleImages() must either return an error string or return info
about which of the 2 GetImage calls and DoStuff returned an error.
 - DoStuff()'s implementation must be aware of the CImageSource
subclasses so that it's error codes do not overlap.
 - Another way of mapping DoStuff() error codes to strings must be
present (if using static ErrorStr the obvious solution to this is to
make ErrorStr be a global function, not a CImageSource static member,
of course -- the problems still exist, though).

On the other hand, using exceptions, the code to handle DoStuff()
errors is *identical* to the code above. DoStuff() throws an
exception, the caller catches it and takes the appropriate action. You
do not need to think about any of these difficulties. You do not need
to add any extra handling logic. Everything just works.

There is another solution to the HandleTwoImages() problem above that
I sometimes see implemented. This solution is to allow each
CImageSource (for example) to define it's own error codes. Then,
define a global set of error codes, some of which duplicate the
meaning of the errors defined by each individual CImageSource but have
different values. Finally, provide a function to map specific error
codes to global ones. You may think this sounds crazy, but this is
actually done very frequently, although generally not for error codes
within an application itself. Consider the actual implementation of
CFOO1200ImageSource, for example. Perhaps the FOO-1200 drivers were
written in C, and all functions return error codes. You have no
control over the values of these error codes, but you are interested
in their specific meanings (for example, the FOO-1200 API defines it's
own error code for "file not found", etc.) in that you want to display
the correct error message to the user. If you want to handle specific
driver error codes as special cases, you must map the driver error
codes to equivalent error codes in your application. In the most
unfortunate case, the driver may even provide an API call to retrieve
the last error string -- however you do not get to take advantage of
this function as you must pass application error codes back to the
application.

Using exceptions can give you a big advantage here. If the FOO-1200
drivers do not provide a driver error -> string function then no,
exceptions will not make the special case handling any more convenient
-- you must still convert driver error codes to exception message
strings per-error. However, if the FOO-1200 drivers do provide error
strings, handling errors with exceptions is as easy as using the
string returned from the drivers in some EFOO1200DriverException. No
special case handling necessary, you do not need to be aware of error
codes at all, there is no transformation from FOO-1200 error space to
application error space, there is no massive lookup table in the
application's ErrorStr() function (for example), there is nothing.
Those are all problems that may come into play specifically when
implementing CHTTPImageSource and CFOO1200Source, and the others. With
exceptions, you can use the minimal amount of device/library-specific
error code -> exception conversion at the lowest level, and after
that, it all just works.

Returning to GrabAndDisplayImage; the new implementation using error
codes (compare this to above implementation using exceptions):

int UI::GrabAndDisplayImage (CImageSource *source) {

    Image img;
    int ret;

    if (!source)
      return IMGSRCERR_NULL_POINTER;

    ret = source->GetImage(img);

    if (ret != IMGSRCERROR_OK)
      ret = display_->DisplayImage(img);

    return ret;

}

void UI::OnButton () {

  int err = GrabAndDisplayImage(source_);

  if (err != IMGSRCERR_OK)
    DisplayError(ErrorStr(err));

}


It's still easy to read, sure, in this simple example. However, it
suffers from every problem described above (and the same collision vs.
vagueness issues now come into play in the implementation of
DisplayImages() as well) -- the amount of thought and care that
actually had to go in to selecting error codes and implementing
ErrorStr() and friends in this case is incredible.

Also something you mat notice. GrabAndDisplayImage now returns
IMGSRCERR_NULL_POINTER if source is NULL. The ErrorStr() function
likely changes this to a message such as "NULL parameter to function".
It must, in order to cover the general case of IMGSRCERR_NULL_POINTER.
The implementation with exceptions, however, threw an
ENullPointerException (thus identifying the general error type) but
with a specific, meaningful message that makes sense in context ("No
image source specified"). You could change the error return code to
IMGSRCERR_NO_SOURCE_SPECIFIED if you want; but then you have lost the
information that it is an error related to NULL parameters being
passed to a function. You could also check for IMGSRCERR_NULL_POINTER
and display the appropriate message in OnButton() -- but that only
works if IMGSRCERR_NULL_POINTER can't be returned for any other
reason. With error return codes, there is no straightforward way to
associate context-specific messages with general errors. You must take
all this into consideration when using error return codes, or else you
start giving the user messages like "Error: I/O error". With
exceptions, you do not have to put any of this kind of thought into
it.

Thus concludes that example. I may have left something out, but I feel
I have said enough to make my point clear. I would not argue whether
or not you "should" use exceptions or "should" use error codes -- I am
a major proponent of using whatever tool is most appropriate, most
convenient, and most familiar to get a job done adequately. Proper
error handling is possible with both methods. Also the above examples
are not the only options -- there are many other ways of expressing
the same things. I wanted to present a relevant example with the hope
of showing that preferring exceptions over error codes can make life a
lot simpler while coding.

There is another example I want to point out, much more briefly: One
thing that you can not do with error codes is return errors from class
constructors. In some cases, this can lead to more complex invariants
on classes and therefore more complex logic to determine the state an
object is in. For example, let's see we have a class ImageBuffer that
can hold some data. Using exceptions, I can do this:

class OutOfMemoryError : public BaseException { ... }
class InvalidDimensionError : public BaseException { ... }

class ImageBuffer {
public:
  // allocates buffer of appropriate size
  ImageBuffer (unsigned w, unsigned h)
    throw (OutOfMemoryError, InvalidDimensionError);
  ~ImageBuffer ();
};

Using exceptions allows me to abort construction of an object on
error. Therefore it is now possible for me to say "if an ImageBuffer
exists, it encapsulates a valid block of memory of the appropriate
size... period." It then becomes reasonable for me to assume that if I
have an ImageBuffer I can use it safely:

void ProcessImages ()
  throw (BaseException)
{

  ImageBuffer a(1000, 1000);
  ImageBuffer b(1000, 1000);

  // do stuff

}

I do not need to verify the state of the ImageBuffers in
ProcessImages. Rather, as long as the caller is handling exceptions
appropriately, everything is taken care of if one of the memory
allocations fails. John Love-Jensen and Ted Byers may likely state
that this is a good example of using exceptions to handle a rare,
fatal -- that is, a memory allocation failure. That is true, but keep
in mind the memory allocation error is used as an example, perhaps a
constructor loads data from a file and throws an exception if the file
does not exists (e.g.) -- this example applies to all of those cases
of "predictable, common errors" as well.

On the other hand, if I am to avoid exceptions I can no longer state
the simple invariant that "if an ImageBuffer exists it is valid". Now,
an ImageBuffer may exist but not be valid. I may implement it like
this:

class ImageBuffer {
public:
  // possibly allocate buffer of appropriate size
  ImageBuffer (unsigned w, unsigned h);
  ~ImageBuffer ();
  int LastErrorCode () const;
};

Now I must jump through the same hoops as in the CImageSource example
above, and additionally I must always check the state of ImageBuffers
(for example, if ImageBuffer has some member function like
RotateImage(), it can not assume valid data, it must check to ensure
that at least some conditions are true before doing it's thing):

int ProcessImages () {

  ImageBuffer a(1000, 1000);
  ImageBuffer b(1000, 1000);
  int e;

  if ((e = a.LastErrorCode()) != 0)
    return e;
  if ((e = b.LastErrorCode()) != 0)
    return e;

  // do stuff

}

Exceptions handle all this logic for you.

>  One way to have more "in your face" return codes, is to return "smart return
>  code objects".
>
>  A smart return code object has this kind of behavior:
>  + wraps a return code
>  + if the caller does read (query) the return code object,
>   THEN the object marks itself as having been checked
>   AND the object's destructor is silent
>  + if the caller does not read (query) the return code object,
>   AND the return code object does NOT have an error status,
>   THEN the object's destructor is silent
>  + if the caller does not read (query) the return code object,
>   AND the return code object DOES have an error status,
>   THEN the object's destructor does "something"
>   Where "something" is one or more of...
>   + an assert
>   + a trace to a log
>   + throw an exception

:-) I hope that you realize how close to a properly designed exception
class this "smart return code" is. See my example above for the
parallels. The advantage that exceptions as used above have over the
"smart return code" you have defined here is they do not suffer from
the collision vs. vagueness problem that I described above. Using
"smart return codes" still requires unique and meaningful error
returns to be defined.

Additionally, your description of a "smart return code" has a lot of
special logic in it. I see a lot of +, AND, and THEN in there. None of
that is necessary when using exceptions. Construct the exception and
throw it. If an exception is caught it was an error.

Most of the logic in your smart return code seems to allow for the
propagation of the error code up the call chain until it is able to be
handled. Exceptions handle this implicitly. Consider:

void SomeFunction ()
  throw (SomeException)
{
  try {
    AnotherFunction();
  } catch (...) {
    throw;
  }
}

It does not matter how deep in the call stack the original exception
originates from. Eventually it will be thrown out of
AnotherFunction(). SomeFunction() then has the option to catch it,
perform it's own part of the recovery process, and then continue
propagating the error upward until it is resolved. SomeFunction() need
not even rethrow the exception -- if SomeFunction() has enough
information to recover from the error completely, it can stop it right
there and continue on it's way.

>  I consider a "smart return code object" to be training wheels for result
>  codes.  But for larger teams, they become very helpful.

The smart return code object as you have defined it could very well be
training wheels for result codes. Proper exceptions provide far more
functionality than smart return codes and error codes. The smart
return code is, in fact, a sort of learning-disabled sibling of an
exception. I mean that in a humorous way. For larger teams, smart
return codes become helpful. Similarly, exceptions become even more
helpful, as they provide all of the benefit of the smart return code,
plus some.

To be honest, while I am not setting out to convince you of one way or
another, I do want to point out that it seems to me that you do, in
fact, see the merit of using exceptions for common, predictable
errors. However, for whatever reason, you are hiding this behind the
"smart return code" mask -- I am guessing that the reason is because
your philosophy is to only use exceptions on fatal errors, but that
exceptions do provide elegant solutions to non-fatal problems, except
to use exceptions directly would violate the philosophy (and so you
have made your own constructs with closely related functionality). I
think that you would have a lot to gain by beginning to favor proper
exceptions over "smart return codes". They're almost the same thing!

For the most part, everything you go on to say is completely
reasonable and/or I addressed it above, although I do have some minor
comments:

>  > And what do you consider to be unpredictable and rare?
>
>  Any violation of the routine's contractual requirements.
>
>  For example, if an int parameter must have the value of 0, 1 or 2, and the
>  value passed in is something other than 0, 1 or 2.
>
>  Any violation of the routine's contractual obligations.
>
>  For example, if the routine must put the object in state A or B, but was
>  unable to do so and the object is in state C (a "never happen" situation).
>
>  For example, if the object is in an inconsistent state upon return, such
>  that the object's invariance is violated.  This can happen if a state change
>  cannot be completed as a transaction, and enough has happened such that the
>  state of the object cannot be reverted.  (That's why have transaction based
>  assignment operator -- perhaps using the swap paradigm -- is so very
>  useful.)

Using exceptions does not make the swap idiom less useful; it provides
implicit exception safety in the same way it provides good error
return safety.

>  Any "never happen" situations.  (Where, in the case of my applications,
>  keeping in mind that throwing an exception means "terminate the application,
>  forthwith".  So throw an exception where it is appropriate to terminate the
>  application works as a rule-of-thumb in my application.  Your exception
>  usage policy will likely be less extreme.)
>
>  Any "is it okay if the processing continues (without throwing or returning
>  an error code) with the detected 'broken axle' condition?"  If it's not
>  okay, then either throw or return an error code.
>
>
>  > If I was writing code to parse that, and found "root", then
>  > "something", then "value", but not "data", I would call that a rare...
>
>  I presume you are speaking of the "meta-layer" (the decision making layer)
>  above the parser itself.
>
>  In my application, ponder "is this failure such that the application should
>  be terminated ... or can it be handled in some reasonable failsafe fallback
>  and the application continue running?"

In the case of the XML example I gave, let's say that not finding the
"data" node was not fatal to the application. Perhaps the user
attempted to load a file from a menu in a GUI and that failed. In this
case the reasonably failsafe fallback is to notify the user of
precisely the condition that caused the error. Nothing else can be
done no matter what the error: the user must load a different file or
somehow fix the offending document with other means. Throwing an
exception with a descriptive error message from the source of the
error, then propagating it up the call chain until something can
handle it appropriately (in this case, by displaying the message in a
message box to the user), handles this elegantly, no matter what the
source of the error. Of course this all relies on constructing
exceptions with useful information in them to begin with.

One important point that this brings to mind is; poorly designed
exceptions will gain you *nothing* over error return codes. In fact,
you may lose something. Take, for example, the Something()'s in my
first message. If used as-is, with no parameters, and if all error
conditions throw a Something(), then really, what can you do with
that? The most you can do is display a message that says "Somewhere,
at some point, some error occurred". That is unacceptable. Exceptions
in themselves do not gain you anything, but exceptions give you a
great framework for simple and powerful error handling and recovery.
Just with any other language construct, "improper" use will render its
potential benefits meaningless.

>  Also useful is writing your own throw handler such that if a throw happens
>  it forks the application and core dumps the child (useful to have
>  "core.<PID>" files enabled).  That's how rarely I expect an exception to
>  occur, even when using exceptions with less draconian policies than
>  "terminate the application, forthwith".  And with a core you have a good
>  snapshot of the application and where things went awry.

Indeed. However, I want to point out that this can also be
accomplished while still using exceptions for lesser errors. Take, for
example, the EBaseException above, which contains some information
about the severity of the error. It would be straightforward to flag
exceptions that should "terminate and dump core" as such. Or when
using the "Recover()" interface that I had mentioned, perhaps you have
one of these:

class EFatalError : public EBaseException {
public:
  virtual void Recover () {
    // ... terminate and dump core ...
  }
}

Of course this makes "Recover" a bad name for the function ;-) let's
call the function "Handle()" instead. Now any exception deriving from
EFatalError could cause the application to terminate, if you choose to
structure your application this way. You can also make the default
implementation of "Handle()" simply display a message box, and now you
have this elegant solution:

void DoSomething () {
  try {
    SomethingThatCouldThrow();
  } catch (EBaseException &x) {
    x.Handle();
  }
}

And that code displays a message box if appropriate, or dumps core if
appropriate, or whatever Handle() does for the exception that was
thrown.

>  Also, once you start using exceptions, you have to be careful too.
>  Exceptions cannot be thrown through a C barrier.  That means that you cannot
>  propagate an exception through an OS callback or other C callbacks.  You
>  cannot propagate an exception outside of a thread's entry routine.  And on
>  some platforms, you cannot propagate an exception out of the "module" (DLL)
>  that generated the exception.

All of this is entirely true. It's a problem that I am familiar with.
There are clever ways to pass exceptions around these boundaries, that
I have used. Error return codes can be passed across such boundaries
with much less of an issue. However, perhaps it is the nature of our
respective work, but I find that the cases where I run into these
boundaries are so relatively rare, that I can not justify them as
reasons to sacrifice the other benefits of exceptions in an
application.

In the case of the C barrier, your only real choice is to keep the
errors as error codes at the lowest level, and only translate to
exceptions on a higher level once you back on the C++ side. This is
not always possible, of course, but some things can make it very easy:
for example, if whatever C API you are using allows your callback
function to return an error code. If the callback in question can
cause some sort of status to eventually be returned to the C++ side of
things, then you can deal with throwing exceptions there. If the
callback can not cause some sort of status to be returned, you are out
of luck -- although IMHO the problem here is more of a shortcoming of
the API you are using. This flavor of solution is also necessary when
the module or threaded code is in C as well.

For thread entry functions and DLLs, there many solutions as well.
Unfortunately you do have to code with awareness of these boundaries
in mind. You could keep errors as error codes on the other side of the
boundary, throwing exceptions only when able. This is what you would
be doing anyways if you were not using exceptions -- you would be
using error codes. Also exceptions are objects just like any other, in
the past I have had a lot of success having threads that must throw
exceptions out of the thread handler store the exception and do no
further processing, and at the next available opportunity pass that
exception object to whatever thread *can* handle it, which then
proceeds to throw the exception (another solution is to have the
thread return a pointer to a new exception object, and defer handling
until the thread terminates) -- in any case error handling in threads
and DLLs using exceptions has all of the same design caveats as
passing any other complex data around.

How you get across these boundaries really does depend on the
situation. Again you have to use the right tools for the job. You can
not throw exceptions out of thread handlers, and so error codes will
help you out more there, at least until the error gets to the point
where you can throw.

>  > Take Java as an extreme example of the use of exceptions.
>
>  Java exceptions are not the same thing as C++ exceptions.
>
>  In my opinion, Java exceptions are much more useful, and robust, and can be
>  used in Java situations where one would not use C++ exceptions in analogous
>  C++.

Can you think of an example that supports this? I do not think that is
a true statement. I think it is more accurate to say that because
exceptions were an important part of Java to begin with, much of the
Java language and APIs has been designed with them in mind, and
therefore they are useful because they are used more frequently and
more effectively. Java exceptions give you nothing that C++ exceptions
do not have. The major problem with C++ exceptions is that they are
frequently used ineffectively -- not that they must necessarily be
ineffective.

For example, Java strictly enforces the throws() clause of a function.
It has done so from the start. Therefore the throws() clause is a very
important part of a a function in Java, it carries a lot of meaning.
In C++, it is very rare to find a compiler that even *attempts* to
have some support for the throw() clause. I use it only as
documentation, basically. Some C++ compilers even issue warnings along
the lines of "throw() clause ignored" when you have it. This is one
example of many reasons why a programmer might construct more useful
exceptions in Java than in C++. Java is a very exception-oriented
language. C++ supports them but the language itself doesn't really
encourage their use in any specific way.

Therefore I feel that many C++ programmers do not understand the
benefits of using exceptions, and proper techniques for effective use,
as well as a Java programmer might. On the other hand, C++ exceptions
do give you that same type of functionality. One feature that I have
always desired the most in a C++ compiler is strict enforcement of
throw() clauses.

Java exceptions are not inherently "better" than C++ exceptions --
rather, exception design in Java should be used as a reference model
for a C++ programmer who also wishes to use exceptions effectively.

>  > on failure. In this case, exceptions are a great error return, because
>  > they can store a lot more information than, say, returning an integer
>  > from a function. In fact, this is *precisely* what exceptions are
>  > designed for.
>
>  An error code from a function does not need to be an int.  The error code
>  can be an object that can store a lot more information.

If you start returning objects from functions on errors, you end up
emulating the functionality of exceptions, but with a different
syntax. Additionally, checking return types of functions rather than
throwing an exception (which automagically branches to a catch, and
also automagically is propagated up the call chain) ends up requiring
error handling logic that can be avoided by using exceptions. Even
more importantly, this begins to interfere with normal function return
values. For example, with exceptions:

  int QueryA () throw (Exception);
  double QueryB () throw (Exception);

These are easy to use, of course. The function declarations are easy
to read and understand. Calling them is simple. It is very clear.
Handling errors is also very clean:

  int a;
  double b;

  try {
    a = QueryA();
    b = QueryB();
  } catch (Exception &x) {
    // handle
  }

The beauty is in the simplicity. You can use the return value from
functions to pass info back. You can catch thrown exceptions to handle
errors. As soon as you start returning error objects, you have to
start adding unnecessary complexity. Even small, small amounts of
complexity, such as this and all it entails:

  ErrorInfo QueryB (double &s); // returns error info

Are unnecessary when using exceptions.

>  > Yes, I know this. This is precisely why I came here to ask about SEH
>  > in GCC... SEH is a feature in other compilers that I use precisely to
>  > prevent the bloat. GCC did not provide it, and now I am looking for an
>  > alternative. You are preaching to the choir, my friend.
>
>  C++ has RAII, which is just as useful, and is standard.  No need for an SEH
>  compiler extension.
>
>  I'm glad you found Boost -- amazingly cool C++ enhancers.  :-)

It's true. There is some really awesome stuff here.

>
>  Sincerely,
>  --Eljay
>
>  Note: Ted Byers is spot on.  I would have answered in exactly the same way
>  as he did, but I doubt I could have answered as eloquently.
>
>  Note: Read Herb Sutter's Exceptional C++.

Got it right here on the shelf, finished. :-)

>  Also, read Herb Sutter & Andrei Alexandrescu's C++ Coding Standards.

I will check this out. I actually might have an eBook version of it
laying around that I have been meaning to look over.

Well, that concludes this week's article.

Jason

[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux