|
Boost : |
Subject: Re: [boost] [outcome] Exception safety guarantees
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2017-05-29 10:58:05
Le 29/05/2017 à 00:15, Andrzej Krzemienski via Boost a écrit :
> 2017-05-28 3:02 GMT+02:00 Emil Dotchevski via Boost <boost_at_[hidden]>:
>
>> On Sat, May 27, 2017 at 4:09 PM, Andrzej Krzemienski via Boost <
>> boost_at_[hidden]> wrote:
>>
>>> 2017-05-28 0:55 GMT+02:00 Emil Dotchevski via Boost <
>> boost_at_[hidden]
>>>> :
>>>> On Sat, May 27, 2017 at 3:15 PM, Andrzej Krzemienski via Boost <> Note
>>> that there is no provision to report a
>>>> failure (to establish the invariants) except by throwing. This is not
>> an
>>>> omission but a deliberate design choice.
>>>>
>>> Interesting. I do not know what you mean here. Maybe, "there is no other
>>> way to report such failure except to throw an exception"? If so, I agree,
>>> but how does this relate to the discussed topic?
>>>
>> What I am demonstrating is that the C++ semantics for initialization and
>> destruction of objects have a built-in assumption about what constitutes a
>> valid object. Can you define this as "the only safe thing to do with x is
>> call is_valid()" on it? Sure, but then you're operating as a C programmer.
>> Consider what a C programmer has to do:
>>
>> struct foo { /*state*/ };
>>
>> void init_foo( foo * x )
>> {
>> /*initialization*/
>> assert(is_valid(x));
>> }
>>
>> void destroy_foo( foo * x )
>> {
>> assert(is_valid(x));
>> //destroy *x
>> }
>>
>> void use_foo( foo * x )
>> {
>> assert(is_valid(x));
>> /*use foo*/
>> }
>>
>> And now compare this to a C++ program:
>>
>> struct foo
>> {
>> /*state*/
>> foo()
>> {
>> /*initialization*/
>> }
>>
>> ~foo()
>> {
>> /*destruction*/
>> }
>>
>> void use()
>> {
>> /*use foo*/
>> }
>> };
>>
>> Note that in well designed C++ programs not only the asserts are not
>> necessary, they're downright silly. Is the object valid? Duh, of course it
>> is, or else the constructor would not have returned. But if you introduce a
>> "not quite valid" state, not only you need the asserts, you're making it
>> much more difficult for the user to reason about the state of the objects
>> in his program, just like a C programmer must.
>>
> Emil, thanks for being patient with me. I understand what you are saying
> here. It is convincing. But ultimately I have found it to be incorrect
> after C++11 introduced move semantics. Let me show you a typical
> implementation of a RAII-like type for representing file-handles. First, in
> C++03, without moves
>
> ```
> class File
> {
> int _handle;
>
> public:
> explicit File(string_view name) : _handle(system::open_file(name))
> {
> if (_handle == 0) throw FileProblem{};
> }
>
> char read()
> {
> // no precondition: _handle always valid
> return system::read_char(_handle);
> }
>
> ~File()
> {
> // no precondition: _handle always valid
> system::close(_handle);
> }
> };
> ```
>
> It is as you say: if we have an object, we know we have a file open, ready
> to be used. But now, lets's add C++11's move semantics:
>
>
> ```
> class File
> {
> int _handle;
>
> public:
> explicit File(string_view name) : _handle(system::open_file(name))
> {
> if (_handle == 0) throw FileProblem{};
> }
>
> File(File && rhs) : _handle(rhs._handle)
> {
> rhs._handle = 0; // now rhs obtains an invalid state (or, not-a-file
> state)
> }
>
> char read()
> {
> // precondition: _handle != 0
> return system::read_char(_handle);
> }
>
> ~File()
> {
> if (_handle) // defensive if
> system::close(_handle);
> }
> };
> ```
>
> Now, because I have a moved-from state, it weakens all my invariants. As
> you say, every function now has a precondition: either I put defensive if's
> everywhere, or expect the users to be putting them.
>
> And this is a normal moveable RAII class (or maybe a movable type is no
> longer "RAII" because of this). And we have lived with it for years now. I
> often have functions returning std::unique_ptr's, and I am not
> defensive-checking everywher if the function did not return a null. I just
> trust that if someone is returning a unique_ptr it is because they wanted
> to return a heap allocated object: not null.
>
> My point: moved-from state is quite similar to valueless_by_exception, it
> exposes the same problems (weak invariants on RAII-like types), and no-one
> complains about it.
Not exactly. moved-from state has finished its operation move with
success. While valueless_by_exception has changed the state on a failure.
Vicente
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk