Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for a snapshot of the develop branch, built from commit 0f79ae966a.
Library Documentation Index

Safe Numerics

PrevUpHomeNext

Programming by Contract is Too Slow

Programming by Contract is a highly regarded technique. There has been much written about it and it has been proposed as an addition to the C++ language [Garcia][Crowl & Ottosen] It (mostly) depends upon runtime checking of parameter and object values upon entry to and exit from every function. This can slow the program down considerably which in turn undermines the main motivation for using C++ in the first place! One popular scheme for addressing this issue is to enable parameter checking only during debugging and testing which defeats the guarantee of correctness which we are seeking here! Programming by Contract will never be accepted by programmers as long as it is associated with significant additional runtime cost.

The Safe Numerics Library has facilities which, in many cases, can check guaranteed parameter requirements with little or no runtime overhead. Consider the following example:

//  Copyright (c) 2018 Robert Ramey
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <cassert>
#include <stdexcept>
#include <sstream>
#include <iostream>

#include <boost/safe_numerics/safe_integer_range.hpp>

// NOT using safe numerics - enforce program contract explicitly
// return total number of minutes
unsigned int contract_convert(
    const unsigned int & hours,
    const unsigned int & minutes
) {
    // check that parameters are within required limits
    // invokes a runtime cost EVERYTIME the function is called
    // and the overhead of supporting an interrupt.
    // note high runtime cost!
    if(minutes > 59)
        throw std::domain_error("minutes exceeded 59");
    if(hours > 23)
        throw std::domain_error("hours exceeded 23");
    return hours * 60 + minutes;
}

// Use safe numerics to enforce program contract automatically
// define convenient typenames for hours and minutes hh:mm
using hours_t = boost::safe_numerics::safe_unsigned_range<0, 23>;
using minutes_t = boost::safe_numerics::safe_unsigned_range<0, 59>;
using minutes_total_t = boost::safe_numerics::safe_unsigned_range<0, 59>;

// return total number of minutes
// type returned is safe_unsigned_range<0, 24*60 - 1>
auto convert(const hours_t & hours, const minutes_t & minutes) {
    // no need to test pre-conditions
    // input parameters are guaranteed to hold legitimate values
    // no need to test post-conditions
    // return value guaranteed to hold result
    return hours * 60 + minutes;
}

unsigned int test1(unsigned int hours, unsigned int minutes){
    // problem: checking of externally produced value can be expensive
    // invalid parameters - detected - but at a heavy cost
    return contract_convert(hours, minutes);
}

auto test2(unsigned int hours, unsigned int minutes){
    // solution: use safe numerics
    // safe types can be implicitly constructed base types
    // construction guarentees corectness
    // return value is known to fit in unsigned int
    return convert(hours, minutes);
}

auto test3(unsigned int hours, unsigned int minutes){
    // actually we don't even need the convert function any more
    return hours_t(hours) * 60 + minutes_t(minutes);
}

int main(int, const char *[]){
    std::cout << "example 7: ";
    std::cout << "enforce contracts with zero runtime cost" << std::endl;

    unsigned int total_minutes;

    try {
        total_minutes = test3(17, 83);
        std::cout << "total minutes = " << total_minutes << std::endl;
    }
    catch(const std::exception & e){
        std::cout << "parameter error detected" << std::endl;
    }

    try {
        total_minutes = test3(17, 10);
        std::cout << "total minutes = " << total_minutes << std::endl;
    }
    catch(const std::exception & e){
        // should never arrive here
        std::cout << "parameter error erroneously detected" << std::endl;
        return 1;
    }
    return 0;
}
example 7: 
enforce contracts with zero runtime cost
parameter error detected

In the example above, the function convert incurs significant runtime cost every time the function is called. By using "safe" types, this cost is moved to the moment when the parameters are constructed. Depending on how the program is constructed, this may totally eliminate extraneous computations for parameter requirement type checking. In this scenario, there is no reason to suppress the checking for release mode and our program can be guaranteed to be always arithmetically correct.


PrevUpHomeNext