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).
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 :))
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.
In addition to knz42's answer, note that destructors for temporary objects are always delayed until the end of the statement, at which point they run in the reverse of the order of the corresponding constructors.
It's like the author knew it was a bad idea too (he put noexcept(false) on the destructor; without which in C++11 the program terminates immediately when an exception is thrown from destructor).
If there's some way to avoid terminating when there's a double exception in the destructor, that would be good to hear!
One simple way I can think of is to reuse the same variable instead.
This will not work, as `throw_on_error` is not going to be destroyed at the end of the printing statement. Sure, you could wrap the whole thing up into a local scope, but as this trick was invented to save a couple of lines of vertical space, the usefulness of doing it seems little.
Kind of OT, but since we're talking language interop, Fortran has neither eager nor short-circuit and/or and it is completely up to the compiler what optimizations it makes. Since Fortran ABI works directly with C (and by extension C++) natively, I've seen a whole class of bugs opened up by C/C++ devs either reading or writing Fortran not aware that this could happen. Try removing that from your mental model of how boolean logic works!
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.
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.
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...
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!
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++?
I'm not sure if this is the author's reason, but one reason is that the C++ wrapper could be implemented entirely in a header file, giving you ABI compatibility that you might not have otherwise.
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).
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.
I find this is the first thing a lot of C++ devs do if the wrappers don't exist. It is actually kind of important in C++ if your app uses exceptions and you want to have exception (somewhat-)safe code. Using the C API directly would require a lot of boilerplate to ensure all allocated things are cleaned up if an exception is thrown.
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.
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++...
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.
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?
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.
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.
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).