Boost logo

Boost :

Subject: Re: [boost] [outcome] Exception safety guarantees
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2017-05-27 22:15:46


2017-05-27 20:56 GMT+02:00 Peter Dimov via Boost <boost_at_[hidden]>:

> Andrzej Krzemienski wrote:
>
>> > Destroy-only is a completely different animal.
>>
>> What? How is that different? Surely, when a basic-guarantee operation
>> failed on object `o`, you cannot safely use all operations on `o` that you
>> were allowed to use before the throw.
>>
>
> Yes, you can. That's the whole point. It's as if someone handed you a
> valid object `o`. You don't know what's inside, but you know that it's not
> destroy-only. It has some value, you just don't know what it is.
>

Ok, but even without any exception you cannot call some functions on your
objects, because individual member functions still have preconditions.
Consider std::optional:

optional<int> o = 1;
use_int(*o); // fine
o.emplace(this_returns_int_but_throws()); // basic exception safety
use_int(*o); // UB, even though o is in "valid state"

>
> Sure, you can abide by the letter of the basic guarantee while trampling
> all over its spirit by littering all member functions with a "!valueless()"
> precondition, but this doesn't change much.
>

Ok, I understand.

>
> In other words, under basic guarantee, r.has_value() returns true or
> false, under destroy-only it crashes.
>
> In yet other words, under basic guarantee, you in general have wide
> contract functions, under destroy-only, you never do (except for the
> destructor and possibly assignment.)
>

I have checked "Exception-Safety in Generic Components" at
http://www.boost.org/community/exception_safety.html

It says, "The basic guarantee: that the *invariants of the component are
preserved*, and no resources are leaked."

I suppose either interpretation is correct, depending on how strong you wan
the invariant to be.

But tell me this. Consider the example with class Man above:

```
struct Man { std::string fist_name, last_name; };
Man m1 = {"April", "Jones"};
Man m2 = {"Theresa", "May"};

try {
  m2 = m1; // suppose it throws
}
catch(...) {
}
```

Object m2 after recovering from stack unwinding may be in the state
{"April", "May"}, which is a "valid state". Would you call it a valid
state? It is "valid" in the sense that reading values from its members does
not cause UB, but its "high-level invariant" (that m1 should refer to an
existing person) is broken. It conveys no useful information. Once you
observe it sneaked out of "stack unwinding bubble", the only reasonable
thing you can do with it is to either reset it to a new meaningful value,
of just continue with stack unwinding until m1 gets out of scope. Maybe you
can see other usages, but I don't. And because I am only interested in
reset or destroy, the fact that I can read values from its members is of no
use to me.

Same with outcome<T>: if I assign to it and it fails:

o1 = o2; // assume basic guarantee

provided I get transactional guarantee, I know what its value is. But if I
get "basic guarantee" as you describe it (valid but unspecfied state), what
good does it make that I can safely call has_value() if the object contains
a different value than o1 or o2 had initially? How can I trust such value
even if I can read it? I can only discard it.

Regards,
&rzej;


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