|
Boost : |
From: Beman Dawes (bdawes_at_[hidden])
Date: 2006-02-17 13:55:22
Each Boost library that calls operating system API's seems to deal with
API error reporting in its own way. That's hard on users, and doubly so
if a particular library's approach doesn't easily support a needed use case.
Boost.Filesystem certainly had that problem, and users reported having
to litter their code with try/catch blocks to cope with it only
supporting error-reporting-by-exception.
I think the two-pronged approach Boost.Filesystem finally settled on
might work well for other library's too. I've written it up as a general
guideline. See attached.
Do other Boosters like the idea of an overall guideline for dealing with
operating system API error reporting?
Do the specific guidelines make sense? How can they be improved?
Comments welcome,
--Beman
Operating System API Error Reporting Guidelines
Introduction
Operating system application program interface (API) functions may encounter
errors. How should these errors be reported by Boost Library functions that
use the operating system API?
Guidelines
Unless otherwise specified, the default versions of all Boost library
functions, except destructors, that call operating system API functions
should check for API errors and report the occurrence of such errors by
throwing an exception of type boost::system_error.
Such functions should also have an overload that takes an additional
argument of type boost::system_error_code& ec. The behavior of this overload
is the same as the non-overloaded version, except that it does not throw an
exception on an API error, and it sets ec to the error code reported by the
operating system, or to 0 if no error is reported.
For functions where the non-overloaded version returns void, the overloaded
version returns an object of type boost::system_error_code with the same
value that ec was set to.
The problem
The traditional C approach to error reporting followed by many operating
systems is for the API function to indicate failure by returning an
out-of-range value, and to set a global variable with an error code
indicating the particular error condition. API functions that do no return
an out-of-range value just set the error code global. These C error
reporting idioms are viewed with distaste by C++ programmers:
* It is far too easy and common for programmers to ignore error returns.
* Global variables are dangerous.
* It is difficult in reading code that ignores errors to know if lack of
checking was done for a well-founded reason or simply by mistake.
* Checking for errors obscures otherwise clean code.
* Requires error checking be done immediately even though the most logical
place to deal with errors may be higher up in the call chain.
* Because the mechanism for reporting errors isn't uniform, coding errors
are a concern as is the need to refer excessively to documentation.
One possible C++ approach is to throw an exception whenever an operating
system API error occurs. The exception can capture the particular error code
from the operating system, or a fine-grained exception hierarchy can provide
a type for each possible error code. This approach solves all of the
problems of the tradition C approach, but suffers from the usual problems
associated with exceptions:
* Very time inefficient (factor of 1000 not uncommon) compared to error
returning codes via return statement.
* Clutters code with try/catch blocks when errors must be dealt with
immediately.
* May be totally inappropriate in some contexts (particularly
destructors).
* Is the wrong idiom when errors are commonplace and unexceptional.
Traditionally, when C++ developers don't feel exceptions are appropriate for
API error reporting, they take several approaches:
* Unconditionally ignore the error.
* Provided a nothrow overload that ignores the error.
* Fallback to the errno approach.
None of these is entirely satisfactory, and the lack of uniformity between
libraries is a user irritation.
Rationale
The default version of library functions, without the additional argument,
throws on errors, ensuring that error checking cannot be ignored and
covering the important use case where error reporting via exception is the
preferred mechanism.
The overloaded version of library functions, with the additional
boost::system_error_code& argument, covers both the use case where errors
are best ignored, and the use case where errors are best reported directly
rather than by exception.
Making error-reporting-by-exception uniformly the default, without any
attempt to prejudge the most commonly usage case, aids learning and avoids
endless arguments over which approach is "best". Providing both
error-reporting-by-exception and error-reporting-by-error-code ensures
common use cases are accommodated, and moves the decision as to which to use
from the library designer to the library user.
Returning a copy of the error code, in the case of functions that otherwise
would otherwise return void, results in cleaner user code. No doing so in
the case of functions already returning a value keeps the interface simple.
History
Experience with the Boost.Filesystem library led to the conclusion that both
error-reporting-by-exception and error-reporting-by-error-code are
important, and which is best is highly context dependent.
_________________________________________________________________
Revised 17 Feb 2006
© Copyright Beman Dawes 2006
Distributed under the Boost Software License, Version 1.0. (See accompanying
file [1]LICENSE_1_0.txt or copy at [2]www.boost.org/LICENSE_1_0.txt)
References
1. file://localhost/C|/boost/site/LICENSE_1_0.txt
2. http://www.boost.org/LICENSE_1_0.txt
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk