Boost logo

Boost :

Subject: [boost] [contract] noexcept and throwing handlers
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2016-08-05 14:13:12


Hello all,

I would like to discuss contracts and exception specifications (e.g.,
noexcept) specifically with respect to what Boost.Contract does.

On one hand, it seems silly to allow a contract failure handler to
throw an exception to only translate such an exception into a call to
terminate() for noexcept functions. However, noexcept can be
considered part of the function contract (namely, the contract that
says the function shall not throw) and contract failure handlers
should not be allowed to break such contract, not even when they are
allowed to throw.

For example consider a noexcept function fclose() that is called from
a destructor without a try-catch statement (correctly so because
fclose is declared noexcept). If fclose() is now allowed to throw when
its preconditions fail, that will cause the destructor ~x() to throw
as well?!

    void fclose(file& f) noexcept
        [[requires: f.is_open()]]
    ...

    class x {
        ~x() noexcept {
            fclose(f_); // f_ might not be open because of some bug...
            ...
        }

        file f_;
    };

(A point similar to the above is also made in N4160, in contrast with
what argued in N4110.)

Functions should honor their exception specifications (noexcept, etc.)
even when their contracts fail and contract failure handlers are
configured to throw. That is what Boost.Contract does (given that
contract checking code appears within the function definition so it is
subject to the exception specifications that appear in the enclosing
function declaration):

    void fclose(file& f) noexcept { // This will call terminate()...
        boost::contract::guard c = boost::contract::function()
            .precondition([&] {
                BOOST_CONTRACT_ASSERT(f.is_open()); // ...even if this throws.
            }
        ;
        ...
    }

    int main() {
        boost::contract::set_precondition_failure(
            [] (boost::contract::from where) {
                // Re-throw exceptions (assertion_failure, user-defined, etc.).
                if(where != boost::contract::from_destructor) throw;
                std::terminate(); // But destructors never throw.
            }
        );
        ...
    }

Also note that a contract framework that allows to install failure
handlers that throw exceptions should also pass some sort of parameter
to the handler function to indicate if the contract failure happened
in a destructor or not. That is so the failure handlers can choose to
never throw for destructors, not even if class invariants (or
postconditions) fails when checked by destructors.

--Lorenzo


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