
Beman Dawes wrote:
"Oleg Abrosimov" <beholder@gorodok.net> wrote
Hello Beman!
The N1975 proposal has a section called "Important Design Decisions". It is a very good place to put a rational why you've chosen the error reporting strategy with system_error.
currently, it has only one sentence "Because filesystem operations often encounter unexpected runtime errors, the library by default reports runtime errors via C++ exceptions, and ensures enough information is provided for meaningful error messages, including internationalized error messages."
It is not enough to understand why you don't follow the way of other portable file system frameworks. For example, in java language there are IOException and its descendants like FileNotFoundException. It can be said that it follows the usual OO-exception handling mantra - define a hierarchy of exception classes and use them to report error conditions from your functions. Furthermore, it can be said that it is a way to abstract low-level system errors and provide only high level logical errors (or exceptions).
Yes, I'm well aware of that approach and was one of the participants in LWG at the time that approach was designed back in the early 1990's.
It seems that you are the right person to ask my questions. See below.
A strength of such a high level approach is that it abstracts away a lot of differences between operating systems. But sometimes programmers need to deal with those lower level details. The set of error conditions that has to be dealt with is also open ended, and exception hierarchies where there is one exception for each error type have trouble dealing with that. There is also the issue of interface bloat - there are a very large number of potential errors, and having an exception type for each one of them become unwieldy.
There is also one more issue on client side. If library provides many exception classes that can be thrown and user is interested in many of them ( he can not just use catch(lib_base_exception&) {...} trick ) then code becomes (1) _very_ bloated and (2) unmaintainable: void some_client_func() try { call_lib_with_many_exceptions(); } catch(exception1& e1) { do_response1(e1); } catch(exception2& e2) { do_response2(e2); } catch(exception3& e3) { do_response3(e3); } catch(exception4& e4) { do_response4(e4); } catch(exception5& e5) { do_response5(e5); } catch(exception6& e6) { do_response6(e6); } 1) code is bloated; 2) this exception handling code is duplicated many times in many of some_client_func's. It means that every time when exception handling code should be changed or exception7 handler should be added one needs to make corrections in many places. It is well known as a "maintenance hell". Actually, this bloating problem quickly arises in production code that tries to use exceptions. Even without libs that publish too many exception types. (std::string, const char*, CImageException, std::exception and catch all handlers are the real example from one of real-world projects I've worked on) In java language, for example, this problem is avoided because compiler enforces exception handling in the place where it can be thrown: int n = 0; try { n = Integer.parseInt("1"); } catch(NumberFormatException e) {} it can not be simply written as: int n = Integer.parseInt("1"); because in this case one must specify NumberFormatException in throws specification of enclosing function. As a consequence, it leads to "handle one exception at a time" principle. In C++ it can not be applied, because every statement can throw and compiler can't help you with it. As a consequence, one ends up with many exception handlers that bloats code as was shown above. Actually, there can be a workaround for the bloating/maintenance problem. I can imagine a higher-level function that gets a function to call and something like mpl map where keys are types of exceptions to handle and values are corresponding exception handlers. In this case my exception handling code becomes: void do_response1(exception1& e1); void do_response2(exception2& e2); void do_response3(exception3& e3); void do_response4(exception4& e4); void do_response5(exception5& e5); void do_response6(exception6& e6); typedef ... exception_handlers_map; void some_client_func() { call<exception_handlers_map>(&call_lib_with_many_exceptions); } Then when one needs to replace do_response5 handler with more sophisticated one (and only for one client_func) then he do apply some mpl algorithm for that and specify the result of this algorithm in call<...>(...) statement. This solution looks promising but in practice it would results in code like this: void some_client_func_impl(); // provides actual implementation that can throw many exceptions void some_client_func() { call<exception_handlers_map>(&some_client_func_impl); } May be not too bad, just one more trampoline function, but it looks like a hack that workarounds some deficiency in C++ exception handling mechanism. Can you please elaborate to improve my understanding of what this deficiency is and how it can be solved in the first place - in language itself? Or maybe I've just missed something in C++'s exception handling machinery?
It is clear from n1975 that you abandoned the OO approach and fall back to the C-style error reporting and handling. Yes there are exceptions, but no hierarchy. And exception handling is done by dispatching on a base of error codes but not on exception types. So, it is not really far from "C" I believe.
That was the intent. To provide a C++ interface that is higher level than the C interface, but not so high level that programmers can't get at the details.
consider the following code: // untested namespace boost { namespace win { class win_specific_error1; //... class win_specific_errorN; } namespace posix { class posix_specific_error1; //... class posix_specific_errorM; } } All these error classes are inherited from filesystem_error. Then client can obtain all information in a typesafe manner: try { call_filesystem_lib(); } catch(win_specific_error10& e) { ... } catch(win_specific_error20& e) { ... } Yes, it bloats both, library interface and client exception handling code, but: 1) library interface is written one time and used many times. For me it means that a huge amount of exception classes hidden in special namespaces is not an issue. It doesn't complicates simple usages when only abstract exceptions like filesystem_error are handled by client and provides a consistent and typesafe way for those who wants all the low level information. 2) bloating on the client side was discussed above and can be solved as was shown. (It needs further discussion though)
One more point is that type safety is abandoned here too.
Not sure what you mean here. Could you give an example?
try { call_filesystem_lib(); } catch (filesystem_error& e) { switch(e.code()) { case 1 : do_responce1(); break; case 2 : do_responce2(); break; default : assert(false); // unknown error occurred! } } can you see typesafety here? I can not. For me it is not different from: int err_code = call_filesystem_lib_from_C(); switch(err_code) { case 1 : do_responce1(); break; case 2 : do_responce2(); break; default : assert(false); // unknown error occurred! } hope you see my point now. To conclude: 1) there seems to be a real deficiency in exception handling mechanism that prevents library authors from providing many useful exception classes. This problem needs further investigation. in the meantime it can be workarounded with approach shown in this message (or better one). 2) filesystem error handling machinery reflects the problem mentioned and falls back to the run-time dispatching based on error code that is not type safe.
I should add here that I agree with your decision, but I think that it shouldn't be made implicit, without a really good explanation and rational. The reason is simple: it changes the paradigm that is written in all OO and/or C++ books.
The plan is to take the feedback from Boosters, improve the details of the interfaces, and present a proposal to the LWG refining the work that has already been accepted. I'll try to add more rationale to that document.
Thanks,
--Beman
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost