Boost logo

Boost :

Subject: Re: [boost] [review] Review of Outcome v2 (Fri-19-Jan to Sun-28-Jan, 2018)
From: Andrey Semashev (andrey.semashev_at_[hidden])
Date: 2018-02-01 13:32:21


On 02/01/18 15:03, Jonathan Müller via Boost wrote:
> On 01.02.2018 12:10, Andrey Semashev via Boost wrote:
>>
>> The important difference between using exceptions and error codes (or
>> Boost.Outcome, I presume) is that in case of exceptions the user has
>> to make an effort to write broken code and the correct code most of
>> the time comes naturally, while with manual error checking it is the
>> other way around. This is the reason why manual error checking is more
>> prone to mistakes in error handling.
>>
>> PS: All that, of course, is given that RAII is ubiquitous. If it's not
>> then error handling is difficult regardless of the tool you use.
>
> This entire mistake would have been prevented, if a proper return type
> was used, for example a sane optional (that doesn't try to be a pointer):
>
> static optional<A> A::create(…) {…}
>
> Then, this will not compile:
>
> auto a = A::create(…);
> a.foo(); // error!
>
> So, you're forced to write:
>
> a.value().foo();
>
> And accessing the value on an optional without checking it should always
> be a red flag and not just written "naturally" (That's why operator->
> for optional is a mistake IMO).

I disagree. The necessity to write ".value()" on every access to `a` is
an overhead, both in user's effort and runtime performance. This,
actually, illustrates my point.

(And no, `operator->` is the most often used way to access the value
stored in an optional in my code, as I'm sure it is in that of many
other people. Test for presence once, use unchecked afterwards.)

> Yes, it's still prune to mistakes, but so are exceptions, it's just a
> little bit more work:
>
> Foo::Foo()
> {
>    try
>    {
>        my_a = A::create(…);
>    }
>    catch (…)
>    {
>        log_error();
>    }
> }

Thing is, you don't usually write that code. You usually catch
exceptions at the point where you can do something about them, which in
this case is the client code that requested an object of `Foo`. So your
typical code would be this:

   Foo::Foo() : my_a(A::create())
   {
   }

   void Foo::bar()
   {
     // my_a is guaranteed to be valid
   }

   try
   {
     auto pFoo = std::make_unique< Foo >();
     pFoo->bar(); // pFoo is guaranteed to be valid
   }
   catch (...)
   {
     // handle errors
   }

Whereas, with manual error handling, all code between the error in
`A::create` and the actual error handler and also `Foo::bar` etc.
becomes "tainted" with checks.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk