
A C error handling style that plays nice with C++ exceptions - lettergram
http://blog.sduto.it/2014/05/a-c-error-handling-style-that-plays.html
======
awalton
A large number of libraries already do this: glib uses GError, and glibmm
wraps it in a very similar fashion.

It's also worth mentioning that it's pretty easy to create the Error object on
the stack, such than an error_free() function isn't necessary, but strings
become a bit more messy in that case (like needing string interning or forcing
all error strings to be static).

~~~
apaprocki
Came to mention GError as well. I actually use the GError implementation/style
to auto-bind GObject based classes to Spidermonkey JS exceptions in a
homegrown generic JS binding layer (not unlike GObjectIntrospection, which
came about much later). When everyone uses this style it doesn't really get in
the way, even when used from C, and prevents cases where APIs don't return
proper error info. Everyone just gets used to putting the error parameter
last. (And code generators are especially good at it :))

------
nitrogen
Response to a comment on the blog post:

    
    
        pubby8    May 3, 2014 at 10:07 PM
    
        Good article, but I'm guessing that using more than one
        ThrowOnError per statement will lead to std::terminate. 
        e.g. in seemingly reasonable code such as:
    
            std::cout << libfoo_create_widgets(1, &c1, ThrowOnError()) << libfoo_create_widgets(2, &c2, ThrowOnError());
    

Does C++ not specify an evaluation order for chained stream insertion
operands? It seems to me as a C programmer that the first ThrowOnError should
be destroyed before the second one gets created.

~~~
knz42
No C++ does not specify evaluation order in this case.

Remember "<<" is just that, an operator.

Only && and || have a mandated evaluation order.

~~~
nitrogen
In that case, is a compiler allowed to instantiate both ThrowOnError objects
before calling either of the C functions?

~~~
huhtenberg
No. Overloaded operators act like functions and functions are sequence points.

~~~
MaulingMonkey
Functions have a sequence point that insists their arguments are evaluated
before the function is called, but having both ThrowOnError objects
constructed before calling any of the other functions would still satisfy that
constraint.

------
fleitz
The error style I've always liked best is that of Objective-C.

    
    
      NSError* error = nil;
      [object doSomething: foo error: &error];
      if(error){
    
      }
    

I find exceptions to be rather overkill for most error type problems to begin
and with the ObjC system it's easy to ignore errors that may not be critical.

    
    
      [object doSomething: foo error: nil];

~~~
cnvogel
Well... using the example from the linked article, it could be made equivalent
to your Objective-C snippet, provided the optional passed error-object/struct
isn't updated only on errors (as the article suggests), but also can be used
to find out if an error happened in the first place.

With a suitable modification, the mentioned example would have to be changed
from...

    
    
        libfoo_widget_container_t container = NULL;
        libfoo_error_details_t error = NULL;
        if (libfoo_create_widgets(12, &container, &error) != libfoo_success) {
           printf("Error creating widgets: %s\n", libfoo_error_details_c_str(error));
           libfoo_error_details_free(error);
           abort(); // goodbye, cruel world!
        }
    

...to...

    
    
        libfoo_widget_container_t container = NULL;
        libfoo_error_details_t error = NULL;
    
        libfoo_create_widgets(12, &container, &error);
        if (LIBFOO_IS_BAD(error)) {
           printf("Error creating widgets: %s\n", libfoo_error_details_c_str(error));
           libfoo_error_details_free(error);
           abort(); // goodbye, cruel world!
        }
    

where LIBFOO_IS_BAD() would be a macro or inline function inspecting the error
object/struct, and might be something like

    
    
        #define LIBFOO_IS_BAD(error)   ((error) && (error)->errno != 0)
    

Cutting out the cruft, it's basically what you had suggested to pre pretty
Objective-C style ;-)

    
    
        /* your example */
        NSError* error = nil;
        [object doSomething: foo error: &error];
        if(error){
    
        }
    
        /* article */
        libfoo_error_details_t error = NULL;
        libfoo_create_widgets(12, &container, &error);
        if (LIBFOO_IS_BAD(error)) {
    
        }
    

EDIT: The linked article already mentions that in the example, error would
only be updated if the function returned something unlinke libfoo_success, so
one could directly convert to your favourite style keeping these semantics!

------
shmerl
C++ is quite portable on its own. But wrapping in C can be useful indeed, for
example to enable using that library with other language, like Rust which can
bind with C, but not with C++. But what is the point of writing something in
C++, wrapping that in C and then wrapping it back in C++? Can't the first
level be used directly from C++?

~~~
DSMan195276
My guess would be that the C wrapper looks different then the underlying C++.
Or more, the C++ exposes a different set of functions and isn't stable, where
the C wrapper is stable. By wrapping the C wrapper, you can also give the C++
wrapper API stability since it only depends on the C API. That of course
depends on what all the C wrapper actually does, and whether a subset of your
internal C++ can be declared 'stable' for your API (And if you want to do
that).

~~~
piokuc
You can use the C wrappers in C++ code, of course, but it's often not very
convenient. For example you normally have to call destructors of the objects
created by the library with explicit calls to some C functions from the C
wrappers API. By adding another layer of the C++ wrappers over the C wrappers
you retain the ABI stability without the awkwardness of C style code in your
C++ client code.

~~~
shmerl
I don't really understand why you can't expose a stable ABI right from the
first C++ layer. It shouldn't be any harder than making such ABI in the third
layer, and will avoid many extra function calls which don't come for free.

~~~
piokuc
It's enough to add a member to a class to break its ABI compatibility. It's
possible to expose a stable C++ API but that normally requires extra classes
implementing the PIMPL idiom, so basically the 3rd, C++ layer described in the
article. PIMPL doesn't require the C API (2nd layer), of course, but if you
need the C API anyway, for calling your lib from scripting languages, for
example, then the 3 layers make sense. Lots of tedious work, but that's C++...

~~~
shmerl
What I mean is, instead of making [Core C++ library] -> [stable C interface]
-> [stable C++ interface] one could make both directly:

[Core C++ library] -> [stable C interface]

[Core C++ library] -> [stable C++ interface]

That would avoid extra overheard in the last case.

~~~
piokuc
As far as the number of layers is concerned it's the same. The advantage of
writing the C++ stable API on top of C API is that both C and (stable) C++ API
have the same functionality/semantics - if you write those two separately on
top of the primary C++ API they can easily diverge.

------
jamesaguilar
Wouldn't it be better if the error details struct were stack allocated by the
caller, rather than being a pointer type that has to be allocated by the error
provider? I guess signal safety might not always be important, but it sure
could be, and this mechanism doesn't work for that, right?

~~~
apaprocki
That could work, too, but could place limits on the type/amount of string
error message returned and the lifetime of the struct. Most implementations
just use the heap so there are no gotchas with what kind of message you can
return back and when you can access it.

~~~
jamesaguilar
Interesting. You could also use a static segment error buffer pool. Presumably
error rates are low enough that contention on a lock would not be a problem.
And during signals, there are no other threads running anyway, I think, so you
could skip the lock.

------
faragon
In my opinion, C++ exceptions should be avoided.

~~~
slacka
FYI, I think most people here are looking for rational arguments or at least
anecdotal stories, not opinions.

