|
Boost : |
Subject: Re: [boost] [system][filesystem v3] Question about error_code arguments
From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2009-10-20 19:48:47
I will reluctantly drag my brain out of holiday mode to put in my 2c
here :)
On Tue, 20 Oct 2009 11:15 -0400, "Beman Dawes" <bdawes_at_[hidden]> wrote:
> A POSIX sub-group is looking at C++ bindings for POSIX.
>
> They are talking about a filesystem library binding at least loosely
> based on Boost.System, Boost.Filesystem, and C++ TR2.
>
> In looking at
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2838.html,
> "Library Support for Hybrid Error Handling (Rev 2)", they are
> concerned about specifying hybrid error handling like this:
>
> void f(error_code& ec=throws());
>
> They would rather see it specified like this:
>
> void f(error_code* ec=0);
[...]
> Although the reference form is usual for C++, the use of anything
> other than the default is limited, and usually in low-level code at
> that.
As background to my point of view, I would say that this experience is
not true for Asio users. The use of the non-defaulted call (or rather,
its equivalent in Asio) is not limited, but widespread.
Where applicable, Asio provides overloads:
void f();
error_code f(error_code& ec);
The throwing overloads are most typically in small programs or at
program startup. Non-trivial programs will tend towards the second,
non-throwing overload. It is the nature of network applications to
react to errors as part of normal control flow, and exception handling
is not a good approach for that.
> Regardless, I'd like to hear Boosters views on the two interfaces
> above. Which do you prefer? Why?
First, allow me to register my strong dislike for the pointer form. In
fact, I am not in favour of any specification that disallows the
existence of two separate overloads: throwing and non-throwing.
This:
void f(error_code* ec = 0);
supports both throwing and non-throwing modes. From the compiler's
point of view, any calling code must also support exceptions and so a
space and/or time cost is paid for unwinding, even if it is not
required.
A two-overload approach lets a library do something like:
void f();
void f(error_code& ec) [[nothrow]];
In user code that calls the second form pays no cost for exception
handling, and the compiler may do more static analysis and
optimisation based on the nothrow attribute. It seems a bit odd to be
quibbling over the cost of comparing pointers when some costs of
exception handling are forced on everybody.
Second, I do not pay the "explosion" of overloads argument. (Yes,
there is a doubling.) The potential "explosion" comes from a throwing
operation with defaulted arguments:
void f(int a, int b = 0, int c = 0);
where ideally the user would like to see the following non-throwing
variants:
void f(int a, error_code& ec);
void f(int a, int b, error_code& ec);
void f(int a, int b, int c, error_code& ec);
However the proposed pointer form would be
void f(int a, int b = 0, int c = 0, error_code* ec = 0);
And so a user must specify all arguments to use the non-throwing form.
If you're happy to require users to do so, then the only non-throwing
reference-based overload required is:
void f(int a, int b, int c, error_code& ec);
I have had some Asio users complain that I have left out some
non-throwing overloads. They now have to be aware of the default
arguments where otherwise they could have ignored them. Therefore, the
"explosion" is a cost that can be paid by a library to improve
usability, but it is not inherent in the overload approach.
However, it's true there is a doubling of overloads. In my opinion it
is a cost worth paying (in specification and implementation) to allow
users to avoid the costs of exception handling. In my experience, it's
not *that* big a cost anyway. So as I said, I'm not in favour of a
specification that excludes that approach.
If there was to be a change to the throwing/non-throwing error_code
idiom, then what I would suggest is putting the error_code parameter
*first*. For example:
void f(int a, int b = 0, int c = 0);
void f(error_code& ec, int a, int b = 0, int c = 0);
This avoids the explosion issue due to defaulted arguments, although
you still get doubling. What's especially nice is that it should work
with C++0x variadic templates, while the other approaches do not
AFAIK.
Cheers,
Chris
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk