
2016-06-15 17:30 GMT+02:00 Lorenzo Caminiti
Hello all,
In the last few years my professional and personal lives changed quite a bit limiting my ability to contribute to Boost. However, in my spare time I slowing continued to revise Boost.Contract hoping to be able to commit it to a Boost release someday (after the library was accepted 3+ years ago... https://groups.google.com/forum/?fromgroups=#!topic/boost-list/jQ7OjAmos_Y ). While doing so I realized that leveraging C++11 lambda functions, Boost.Contract could be re-implemented without its crazy macros that alter C++ function declaration syntax.
Boost.Contract still remains the only C++ library that implements *all* features of Contract Programming (a.k.a., Design by Contract or DbC): subcontracting, class invariants (also static and volatile), postconditions (with old and return values), preconditions, customizable actions on assertion failure (terminate, throw, etc.), optional assertion compilation, disable assertion checking while already checking other assertions (to avoid infinite recursion), etc. Furthermore, in its new incarnation Boost.Contract no longer uses crazy macros so it is easier to use, faster to compile, and gives readable compiler errors.
For example:
#include
int inc(int& x) { int result; boost::contract::old_ptr<int> old_x = BOOST_CONTRACT_OLDOF(x); boost::contract::guard c = boost::contract::function() .precondition([&] { BOOST_CONTRACT_ASSERT(x < std::numeric_limits<int>::max()); }) .postcondition([&] { BOOST_CONTRACT_ASSERT(x == *old_x + 1); BOOST_CONTRACT_ASSERT(result == *old_x); }) ;
return result = x++; // Function body. }
Or with subcontracting:
#include
#include <vector> template<typename T> class pushable; // Arbitrary base to demo subcontracting.
template<typename T> class vector #define BASES public pushable<T> : BASES { public: typedef BOOST_CONTRACT_BASE_TYPES(BASES) base_types; // Subcontracting. #undef BASES
void invariant() const { // Checked in AND with base class invariants. BOOST_CONTRACT_ASSERT(size() <= capacity()); // Line 25. }
virtual void push_back(T const& value, boost::contract::virtual_* v = 0) /* override */ { boost::contract::old_ptr<unsigned> old_size = BOOST_CONTRACT_OLDOF(v, size()); // Old values. boost::contract::guard c = boost::contract::public_function< override_push_back>(v, &vector::push_back, this, value) .precondition([&] { // Checked in OR with base preconditions. BOOST_CONTRACT_ASSERT(size() < max_size()); // Line 35. }) .postcondition([&] { // Checked in AND with base postconditions. BOOST_CONTRACT_ASSERT(size() == *old_size + 1); // Line 38. }) ;
vect_.push_back(value); // Function body. } BOOST_CONTRACT_OVERRIDE(push_back) // For `override_push_back`.
// Could program contracts for those as well. unsigned size() const { return vect_.size(); } unsigned max_size() const { return vect_.max_size(); } unsigned capacity() const { return vect_.capacity(); }
private: std::vector<T> vect_; };
Full documentation and examples at: https://lcaminiti.github.io/boost-contract
What do you think?
Thanks, --Lorenzo
P.S. The library can also be used without C++11 lambda functions (or any C++11 specific feature) but programmers have to write a fare amount of extra code to program the precondition and postcondition functors (using non-local functions, Boost.LocalFunction, Boost.Funsion, Boost.Lambda, or some other approach) so that might not useful in practice.
If I understand correctly, the change you propose moves the assertions from function declaration into function definition. If this is the case, I would say it is a step in wrong direction. Pre-/post-conditions are part of function contract and belong in function declaration. Also, from the example, it looks like I will have to pay for the existence of `boost::contract::old_ptr` and `boost::contract::guard` (and compiler warnings related to them) even if I disable contracts. Is that right? I do not know what the previous macros did, but I had the impression that they were able to erase any track of contract-related objects. Also, the way you use the return value, forces me to depart from how I normally write return statements. While the new version is clearly more readable, it also has certain important disadvantages compared to the previous version. What more do the new version of pre/postconditions offers compared to putting two asserts at the beginning and the end of the function? Preconditions are evaluated after local object's destructors. What you propose is addressing the subject from a different anle, but is not necessarily superior to the previous version. Regards, &rzej