Re: GException notes




> > Here I would suggest that Exceptions are Objects in a sense and
> > thus should have a dtor.  The dtor is something as simple as 
> > 
> > typedef void (*GExceptionDtor)(GException* exp);
> > 
> > This pointer would then be in the Exception itself.  
> > 
> > Thus if the memory was allocated by malloc, new, g_new, or
> > static for the str all could be handled properly.
> > 
> 
> I don't see a reason to support allocing the exception without using
> g_exception_new(). What's the benefit? Just avoiding a strdup() which
> is a pointless optimization and discourages creating a nice printf()'d
> error message with additional information. It increases complexity for
> no reason.

Then I don't understand how you intend to handle the memory at all.
You are going to take a const format string and a set of arguments
and print into some sort of char array.  Now unless that is a 
static array for all exceptions, this means that that for some it
must be duped.  

What about languages wrappers which are going to return these
exceptions potentially?  They will likely allocate resources
differently and thus need a dtor to clean up their memory.

The other key advantage to having a dtor is that the exception
can be derived if need be.  (Thus allowing bindings freedom to
pass additional info through if they need to.)

> > I don't understand the need to actually pass the exception pointer.
> > Another valid solution would be to have a global (or thread keyed
> > global.)
> >
> 
> Global doesn't work at all unless you make it per-thread. Per-thread
> global is a possibility. Note however that it doesn't gain you much;
> you still have to zero or clear the global at the start of each
> function, unless you require error functions to have a return code
> indicating whether an error occurs.

Yes, but then it isn't quite so transparent.  

 
> Requiring return codes is somewhat gross; it often leads to an
> unnatural API (for example gconf_get_bool() couldn't return a bool).

I can agree with this.  However, I don't see the requirement as
being there.  The exception checking is outside the error code.
We are really just talking about extra return info just like I
did in libsigc++ for the return code ignore.
 
> I think requiring a return code is a natural consequence of using a
> global error object, because if you don't require them then you have
> to require the error object to be cleared by the caller or the callee,
> and then you lose the only real advantage of a global object (the fact
> that you can ignore it). i.e. g_exception_catch() is no easier to type
> than GException* exc = NULL.
>  
> > #define g_exception_pass() if (g_exception_is_set()) return
> > #define g_exception_pass_val(X) if (g_exception_is_set()) return (X)
> >
> 
> These need the word "return" in the name of them.
> g_return_if_exception() or something.

Okay, naming is not my specialty.  (Code analysis is)

> Note that you can just as well have:
> 
>  #define g_return_if_exception(exc) if (exc != NULL) return
> 
> > It seems to me that adding lots of exception pointers to the end
> > of functions will just make life very hard, especially for connections
> > and backward compatibility. 
> 
> Potentially. I think it doesn't matter for backward compat because
> very few glib/gtk functions actually need exceptions. I don't know how
> it affects connections negatively.

Well, you connect signals to functions.  If you add exceptions to 
functions you change their profile and thus make those impossible to
connect, even when the user doesn't care about the exception code
or they want the exception farther up in the chain.  It adds
the additional complexity that signals man have some connections
with exceptions and some which do not.  

(Of course I am not really sure where the exception code is really
being used.  

Ouch I got my try/catch backwards... tells you how much I have used
them.  I have avoided them like the plague in C++ because they are
10 times slower than using a thread private key.
  
> >   g_exception_catch();
> >   frobate(blah,blah2,NULL);
> >   g_exception_try(&ferby, FerbyHandler);
> >   g_exception_try(&frobate, NULL);
> 
> To me it's very unclear what is happening in this code. I can guess
> what you mean, but have no real idea. It also looks like you have to
> write a separate error handling routine. Compare to:

Well, we could go with the really evil macro magic...

#define g_return_rethrow() if (g_exception_is_set(NULL)) return
#define g_exception_catch(X)  if (g_exception_capture(X))
#define g_exception_catch_all()  if (g_exception_capture(NULL))

void frobate(int i, int j, int k)
{
  if (!do_something())
    {
      g_exception_set(&frobate, "do something failed on %s", str);
      return;
    }
}

void foo()
{
  g_exception_try();  /* clears 
  frobate(1,2,3);
  g_exception_catch(&ferby) {}
  else g_exception_catch(&frobate)
     {
       GException *exp=g_exception_caught();
       /* handle it here */
       
     }
  else g_return_rethrow();

  g_exception_try();
  frobat(2,3,4);
  g_exception_catch_all() {}
}

Where catch moves to exception to caught and thus there is never any
need to explicitly destroy.  Set destroys the current if it was 
never caught.  Catch destroys the caught and make the set NULL.  And
so forth.

 
>  GException* exc = NULL;
> 
>  frobate(blah, blah2, &exc);
> 
>  if (exc != NULL)
>    {
>       /* handle error */
>       ;
>    }
> 
> Here it is very transparent what's going on. To me this is a huge
> benefit.

I think transparency is a very bad thing in a system.  Then the
user will come to depend on the particulars of the implementation
and thus ignore the interface.  (This is my biggest grip with 
Glibs list.)  Transparency should not be a goal!

 
 
> An alternative approach is to set a global error object instead of
> ignoring errors whenever the user passes NULL for the GException**
> argument. Then you can basically have both APIs. I'm not sure if the
> functionality duplication is worth it.

This still leaves the problem of do we make a system where the
signal system is dealing with exceptions.   They are very 
problematic to add arguments.  


> I do think the global object basically works, preferring the final
> function argument approach is a subjective decision.
 
> An advantage of the GException** argument is that the API requires the
> programmer to think about errors, which is IMO a good thing, and Java
> does this for example (it won't compile if you don't handle or rethrow
> all the exceptions a method can throw). With GException** args you
> have to at least pass NULL and explicitly say you don't care about
> errors.
 
> Of course an advantage of a global object is that you can ignore
> errors entirely, but I think this is at best a dubious benefit. It's
> actively broken to do this within a function that can raise errors
> (since you are required to clear any nonfatal errors), and it is most
> likely broken in user code (because if an API raises exceptions it
> should be doing so because the exceptions matter, if the exceptions
> can safely be ignored it shouldn't be raising them). Ignoring errors
> by passing NULL for the error object is not burdensome, and makes it
> hard to accidentally ignore them.

True, but it would absolutely kill the gtk-- language binding.  
We have made the explicit assumption that every argument has a
corresponding C++ type.  Adding optional arguments means there is
no correspondence.  (Okay so this is my own personal headache, but
I think that others will find it similar.)
 
> Another advantage of GException** is its similarity to the way CORBA
> works.

The problem with adding exceptions as such is that we are heading down
the road of admitting that C isn't good enough.  Exceptions, typesystems,
signals are all components provided by java, C++, ObjC.  It feels a
lot like reinventing the wheel here.  Exceptions are something 
best left to the compiler.

--Karl 



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