[contract] Strong exception safety guarantees
Hello all, I've been considering adding a .except(...) construct to Boost.Contract that will allow to specify conditions to check at function exit but when the function body throws (in contrast to postconditions that are checked at function exit but only when the function body does not throw). I think .except(...) could be used to assert strong exception safety guarantees... (note that class invariants are already checked at exit of public functions and also when the public function bodies throw, but invariants are suited to assert only basic, and not strong, exception safety guarantees). That might help addressing a point made in P0147R0 under the "Contracts and Exceptions" section: ``Some operations make stronger exception safety claims. In particular, they claim that an exception thrown from a member function will leave the state of an object unchanged from its state on function entry. If we invalidate postconditions on exceptions, we appear to have no mechanism to describe strong exception safety.'' For example, the following swap ensures to leave its arguments unchanged when it throws: void swap(std::string& a, std::string& b) { boost::contract::old_ptrstd::string old_a = BOOST_CONTRACT_OLDOF(a); boost::contract::old_ptrstd::string old_b = BOOST_CONTRACT_OLDOF(b); boost::contract::guard c = boost::contract::function() .postcondition([&] { // Checked at function exit but only if body does not throw. BOOST_CONTRACT_ASSERT(a == *old_b); BOOST_CONTRACT_ASSERT(b == *old_a); }) .except([&] { // Checked if body throws (strong exception safety guarantees). BOOST_CONTRACT_ASSERT(a == *old_a); BOOST_CONTRACT_ASSERT(b == *old_b); }) ; ... // Body implementation (which might throw). } Would this be beneficial? Thanks, --Lorenzo
2016-07-15 6:04 GMT+02:00 Lorenzo Caminiti
Hello all,
I've been considering adding a .except(...) construct to Boost.Contract that will allow to specify conditions to check at function exit but when the function body throws (in contrast to postconditions that are checked at function exit but only when the function body does not throw).
I think .except(...) could be used to assert strong exception safety guarantees... (note that class invariants are already checked at exit of public functions and also when the public function bodies throw, but invariants are suited to assert only basic, and not strong, exception safety guarantees).
How would you express the strong guarantee on std::vector::push_back? Regards, &rzej;
On Fri, Jul 15, 2016 at 12:35 AM, Andrzej Krzemienski
2016-07-15 6:04 GMT+02:00 Lorenzo Caminiti
: I've been considering adding a .except(...) construct to Boost.Contract that will allow to specify conditions to check at function exit but when the function body throws (in contrast to postconditions that are checked at function exit but only when the function body does not throw).
I think .except(...) could be used to assert strong exception safety guarantees... (note that class invariants are already checked at exit of public functions and also when the public function bodies throw, but invariants are suited to assert only basic, and not strong, exception safety guarantees).
How would you express the strong guarantee on std::vector::push_back?
vector::push_back exception safety:
``If no reallocations happen, there are no changes in the container in
case of exception (strong guarantee).
If a reallocation happens, the strong guarantee is also given if the
type of the elements is either copyable or no-throw moveable.
Otherwise, the container is guaranteed to end in a valid state (basic
guarantee).
If allocator_traits::construct is not supported with val as argument,
it causes undefined behavior.''
http://www.cplusplus.com/reference/vector/vector/push_back/
Hmm... so assuming the allocator API can be augmented with an
allocations() const query that returns the number of allocations done
so far, and that operator== can be used to check if the vector has not
changed, maybe something like this:
template<typename T>
void vector<T>::push_back(T const& value) {
boost::contract::old_ptr
2016-07-15 19:22 GMT+02:00 Lorenzo Caminiti
On Fri, Jul 15, 2016 at 12:35 AM, Andrzej Krzemienski
wrote: 2016-07-15 6:04 GMT+02:00 Lorenzo Caminiti
: I've been considering adding a .except(...) construct to Boost.Contract that will allow to specify conditions to check at function exit but when the function body throws (in contrast to postconditions that are checked at function exit but only when the function body does not throw).
I think .except(...) could be used to assert strong exception safety guarantees... (note that class invariants are already checked at exit of public functions and also when the public function bodies throw, but invariants are suited to assert only basic, and not strong, exception safety guarantees).
How would you express the strong guarantee on std::vector::push_back?
vector::push_back exception safety:
``If no reallocations happen, there are no changes in the container in case of exception (strong guarantee). If a reallocation happens, the strong guarantee is also given if the type of the elements is either copyable or no-throw moveable. Otherwise, the container is guaranteed to end in a valid state (basic guarantee). If allocator_traits::construct is not supported with val as argument, it causes undefined behavior.''
http://www.cplusplus.com/reference/vector/vector/push_back/
Hmm... so assuming the allocator API can be augmented with an allocations() const query that returns the number of allocations done so far, and that operator== can be used to check if the vector has not changed, maybe something like this:
template<typename T> void vector<T>::push_back(T const& value) { boost::contract::old_ptr
old_me = BOOST_CONTRACT_OLDOF(*this); boost::contract::old_ptr<unsigned> old_allocations = BOOST_CONTRACT_OLDOF(get_allocator().allocations()); boost::contract::guard c = boost::contract::public_function(this) ... // Preconditions and postconditions. .except([&] { if( get_allocator().allocations() == *old_allocations || is_copyable<T>::value || is_nothrow_movable<T>::value ) BOOST_CONTRACT_ASSERT(*this == *old_me); // Otherwise, just basic guarantees as asserted by the class invariants. }) ; ... // Body implementation. }
I'm not 100% sure... what do you think?
It bothers me that I have to copy the entire vector upon each push_back. Because OLDOF stuff must do a copy, right? I do not think I could afford that; even in debug builds. Regards, &rzej;
2016-07-15 19:37 GMT+02:00 Andrzej Krzemienski
2016-07-15 19:22 GMT+02:00 Lorenzo Caminiti
: On Fri, Jul 15, 2016 at 12:35 AM, Andrzej Krzemienski
wrote: 2016-07-15 6:04 GMT+02:00 Lorenzo Caminiti
: I've been considering adding a .except(...) construct to Boost.Contract that will allow to specify conditions to check at function exit but when the function body throws (in contrast to postconditions that are checked at function exit but only when the function body does not throw).
I think .except(...) could be used to assert strong exception safety guarantees... (note that class invariants are already checked at exit of public functions and also when the public function bodies throw, but invariants are suited to assert only basic, and not strong, exception safety guarantees).
How would you express the strong guarantee on std::vector::push_back?
vector::push_back exception safety:
``If no reallocations happen, there are no changes in the container in case of exception (strong guarantee). If a reallocation happens, the strong guarantee is also given if the type of the elements is either copyable or no-throw moveable. Otherwise, the container is guaranteed to end in a valid state (basic guarantee). If allocator_traits::construct is not supported with val as argument, it causes undefined behavior.''
http://www.cplusplus.com/reference/vector/vector/push_back/
Hmm... so assuming the allocator API can be augmented with an allocations() const query that returns the number of allocations done so far, and that operator== can be used to check if the vector has not changed, maybe something like this:
template<typename T> void vector<T>::push_back(T const& value) { boost::contract::old_ptr
old_me = BOOST_CONTRACT_OLDOF(*this); boost::contract::old_ptr<unsigned> old_allocations = BOOST_CONTRACT_OLDOF(get_allocator().allocations()); boost::contract::guard c = boost::contract::public_function(this) ... // Preconditions and postconditions. .except([&] { if( get_allocator().allocations() == *old_allocations || is_copyable<T>::value || is_nothrow_movable<T>::value ) BOOST_CONTRACT_ASSERT(*this == *old_me); // Otherwise, just basic guarantees as asserted by the class invariants. }) ; ... // Body implementation. }
I'm not 100% sure... what do you think?
It bothers me that I have to copy the entire vector upon each push_back. Because OLDOF stuff must do a copy, right? I do not think I could afford that; even in debug builds.
My point being: the idea is theoretically sound, but seems impractical. This is simply my impression. My personal view on DbC is that only the preconditions are really important, as they help discover misunderstanding and miscommunications between library/component authors and users. Postconditions are mostly useful (in my view) in that they can be matched with other functions' preconditions. In this view, the "expect" functionality is even more remote to preconditions (and hence, of less practical use). Regards, &rzej;
On Fri, Jul 15, 2016 at 10:56 AM, Andrzej Krzemienski
2016-07-15 19:37 GMT+02:00 Andrzej Krzemienski
: 2016-07-15 19:22 GMT+02:00 Lorenzo Caminiti
: On Fri, Jul 15, 2016 at 12:35 AM, Andrzej Krzemienski
wrote: 2016-07-15 6:04 GMT+02:00 Lorenzo Caminiti
: template<typename T> void vector<T>::push_back(T const& value) { boost::contract::old_ptr
old_me = BOOST_CONTRACT_OLDOF(*this); boost::contract::old_ptr<unsigned> old_allocations = BOOST_CONTRACT_OLDOF(get_allocator().allocations()); boost::contract::guard c = boost::contract::public_function(this) ... // Preconditions and postconditions. .except([&] { if( get_allocator().allocations() == *old_allocations || is_copyable<T>::value || is_nothrow_movable<T>::value ) BOOST_CONTRACT_ASSERT(*this == *old_me); // Otherwise, just basic guarantees as asserted by the class invariants. }) ; ... // Body implementation. }
I'm not 100% sure... what do you think?
It bothers me that I have to copy the entire vector upon each push_back. Because OLDOF stuff must do a copy, right? I do not think I could afford that; even in debug builds.
Absolutely, that's a very valid concern. If I decided to put such an
expensive except contract in real code, I'd probably guard it with
some sort of "audit" (or even "axiom") condition:
template<typename T>
void vector<T>::push_back(T const& value) {
#if CONTRACT_AUDIT
boost::contract::old_ptr
My point being: the idea is theoretically sound, but seems impractical. This is simply my impression. My personal view on DbC is that only the preconditions are really important, as they help discover misunderstanding and miscommunications between library/component authors and users. Postconditions are mostly useful (in my view) in that they can be matched with other functions' preconditions. In this view, the "expect" functionality is even more remote to preconditions (and hence, of less practical use).
True. I'd also add the usefulness of class invariants (checked at entry or exit, but probably not both) in between the one of preconditions and postconditions. Actually, something similar to this was already discussed by Bertrand Meyer in Object Oriented Software Construction and then more by Mitchell & McKim in Design by Contract, by Example (books from 1990s and early 2000s respectively). That said, no reason for the framework to not offer class invariants, postconditions, and maybe even except along the side of preconditions. Some code might not even use preconditions because of performance, and that's OK. Some (most?) code will limit to using preconditions and maybe (exit) invariants. Other code (not much?) might also use postconditions and maybe even except (probably together with some "audit" or similar guards). Depending on the application, programmers can freely chose which part of the framework to use. Thanks. --Lorenzo
participants (2)
-
Andrzej Krzemienski
-
Lorenzo Caminiti