On Tue, Feb 9, 2016 at 3:41 PM, Gavin Lambert
On 10/02/2016 09:04, Emil Dotchevski wrote:
The use of error codes in low level APIs is a fact of life, since most of that code is written in C anyway. I wouldn't call it optimal in C++, I personally convert error codes into exceptions at the earliest possible opportunity. As for runtime overhead, like I said already, where it matters it can be easily deleted, by inlining; and, in such contexts you need to inline with or without exception handling: I've never seen a case where I could afford the overhead of a function call but could not afford the exception handling overhead in the same function.
The overhead of exception-handling plumbing that is not actually exercised is typically minimal and not worth worrying about unless you're in one of those contexts where you begrudge every method call, as you've said.
The overhead of a thrown exception is larger, particularly on those systems where throwing an exception also captures a stack trace (which is incredibly helpful for debugging, but sadly often quite expensive).
True, but I'm yet to encounter a case where I'd care about that overhead. And I don't particularly care how that time compares to the amount of time the successful path takes, since both of them typically vary significantly from one platform to another. Assuming we're dealing with an exceptional situation (something went wrong), that's not a problem. Besides, I'd happily agree -- in the presence of profiler evidence -- that if in a given use case exception handling overhead causes problems which can not be alleviated by inlining a few functions, it shouldn't be used. This is not because returning error codes is "better": in some cases we also do need to resort to writing hand-crafted platform-specific assembly, but we shouldn't argue that we can't use C++ in general because of overhead.
The overhead of dealing with error return codes for uncommon failures is also fairly large, with the risk of ignoring them and ending up in weird states.
The trick is to find the balance between them, with the complication that it may not be known at the library level which failures are exceptional and which are expected. As an example, I can imagine that most applications don't want a "delete file" method to throw an exception if the file is already absent -- but there might be some transactional systems in which that would be a serious problem, and checking for existence beforehand might not be sufficient as it would still be a race condition.
This reasoning is completely orthogonal to the error reporting design -- it has to do with correctly defining the postconditions of the function, which as you point out is domain-specific. Exceptions are used specifically to enforce postconditions. If the correct postcondition of delete_file is "the file does not exist", then it is incorrect to throw if the file was missing to begin with.
This sort of thing could be a good argument for "degrees of success", where eg. the above delete call returns success in both cases but can also indicate that the file was already missing and it did nothing. (Which is trivial in this case since it can be done with a simple bool return value, but you can probably imagine other cases where it might make sense to return both a T and a non-error status value of some kind.)
Assuming the correct postcondition for delete_file is "the file does not exist", and assuming that knowing if the file existed is either important or trivial for delete_file to detect, the correct interface of that function is: //Postconditions: the specified file does not exist. //Returns: true if the file was actually deleted, false if it did not exist //Throws: delete_file_error bool delete_file( char const * name ); This is not "degrees of success", as the condition in which the file did not exist is not an error. Emil