BCOYOTE_ARRAY_CHECK_INDEX(n) // not here unless LIBCOYOTE_BOUNDS_CHECKING is defined return m_array[n]; } template <typename Type> inline Type array<Type>::operator [] (size_t n) const LIBCOYOTE_ARRAY_EXCEPTIONS { LIBCOYOTE_ARRAY_CHECK_INDEX(n) // not here unless LIBCOYOTE_BOUNDS_CHECKING is defined return m_array[n]; }

As always, I'm interested in your comments.

- Scott




E-mail
LinkedIn
Facebook

•• HIRE SCOTT ••

Computer Books
Fiction
Articles
Reviews

FAQ
Bibliography


Link to Scott Ladd's Syraqua site

© 2010
Scott Robert Ladd
All rights reserved.
Established 1996


The grey-and-purple dragon logo, the blue coyote logo, Coyote Gulch Productions, Itzam, Evocosm, and Acovea are all Trademarks of Scott Robert Ladd.

Privacy Policy
Legal Stuff

class="head5center">
by Scott Robert Ladd
1 July 2002
 

Index
  Assert
  Resource Allocation
  Validator
  Requirements
  Design & Implementation
  Usage
Sidebars
  Exceptions
  Namespaces
  Templates
Documentation
  libcoyote (all classes)
Downloads
  libcoyotl-3.0.0.tar.gz

Design by Contract (DBC) is an programming concept introduced by Bertrand Meyer in his object-oriented Eiffel programming language. The "contract" consists of truths that must be maintained in a valid program; these truths come in three flavors:

Bertrand Meyer's Eiffel directly supports Design by Contract, as does Walter Bright's D language. C++, like the majority of programming languages, does not include built-in support for DBC. That doesn't mean, however, that C++ isn't capable of DBC. I'll start with facilities inherited from C, then move on to object-oriented solutions built with C++ for broader validation schemes.

Assert

When validating function arguments or resource allocations, many C++ programmers use the Standard C assert macro (defined in assert.h or cassert), or they employ a platform-specific variant thereof. Here's a typical use of assert, watching for a NULL pointer in a simple string function:

char * clone_string(char * input)
{
    // This is a precondition
    assert(input != NULL);

    // do something with input
    char * result = strdup(input);

    // This is a post condition
    assert(result != NULL);

    return result;
}

When its expression evaluates to false, assert displays implementation-specific information about the error's location before calling the standard function abort. You can make assert "go away" by defining the NDEBUG symbol during preprocessing; doing so defines assert as a null statement, thus removing error checks from your code. In theory, this allows you to use asserts during debugging, removing them with NDEBUG for a production compile.

It isn't uncommon to find coding standards that insist on using assert to implement some form of Design by Contract. In my experience,  Design by Contract is good; assert, however, is bad. Here's why:

Reliable code uses several techniques to avoid runtime errors; assert is the least desirable choice in most cases (albeit more desirable than failing to check for any errors, of course!)

Controlling Resource Allocation

One way to avoid resource problems is the use of an idiom known as Resource Allocation Is Initialization, or RAII. That rather cumbersome term refers to the use of constructors and destructors in controlling resource acquisition. A well-written program encapsulates allocatable resources -- dynamic memory, files, and network connections, for example -- in classes. The class constructor acquires and validates the resource; the destructor performs clean-up and releases said resource. For example:

class RIAAExample
{
public:
    // constructor
    RIAAExample()
    {
        // allocate memory
        m_pointer = new int;

        // set value
        *m_pointer = 0;
    }

    // do something with resource
    void set(int n)
    {
        *m_pointer = n;
    }

    int get()
    {
         return *m_pointer;
    } 

    // destructor
    ~MyFile()
    {
        // free memory
        delete m_pointer;
    }

private:
    // a resource -- in this case, dynamically-allocated memory
    int * m_pointer;
};

int main()
{
    // create an object that allocates a resource
    RIAAExample example;

    // use the object
    example.set(1);

    // example destroyed -- and memory freed -- automatically
    return 0;
}

Memory isn't the only resource that can be safely and effectively managed through classes. Whenever possible, I encapsulate files, database connections, and network communications in classes. Essentially, any resource that is acquired at runtime is a candidate for RAII. 

C++ solidly supports RAII design, but "improved" languages like Java and C# do not. C++ has deterministic finalization, where you explicitly control the lifetime of an object. The compiler keeps track of when your code destroys an object, either by the end of scope or through a call to delete; the destructor will always be called at a specific execution point, guaranteeing resource releases. A well-written C++ program will wrap all resource allocations in classes, eliminating many common and costly program errors.

Java, C#, and Visual Basic.NET do not support deterministic finalization; even if you explicitly destroy an object, it will not actually be "destructed" until the automatic garbage collector kicks in. Sure, you can put code in a Java class's finalize method -- but you have no control over when that code actually gets executed. This is an example of where Java throws the baby out with the bath water, eliminating the ability to control object lifetimes to stop programmers from making pointer errors.

IN Java and C#, you can define member functions or methods to release resources, but the responsibility falls on your shoulders to call those functions at appropriate points. One selling point of automatic garbage collection is that the runtime environment (the Java or .NET "machine") tracks object lifetimes for you; that "advantage" loses much of its value when you must track object lifetimes so you can deliberately release resources. And what happens if your "reference count" for a resource object is different from the one maintained by the machine? How is calling a hand-tooled "Release" or "Destroy" function any better than using the oft-maligned "delete"?

I prefer the C++ model of resource management through RAII. Automatic garbage collection can be a Good Thing -- and C++ allows me to define when and where I need it, while "virtual machine" languages impose an uncontrollable (and potentially troublesome) model. In a future article, I'll look at smart pointers for C++.

Validator: An Alternative to Assert

Given my desire to use Design by Contract and the weaknesses of assert, I created a set of function templates and simple macros that provide validation using exceptions. This is a formalization of tools and techniques I've used for many years in professional applications.

Requirements

Validation will:

  1. ...be obvious and easy to use.
  2. ...support equality, inequality, range, and function-based predicates.
  3. ...be portable across platforms and compilers, producing identical results in all environments.
  4. ...allow complete control over how an error is handled -- by aborting, or displaying an error message, or automatically correcting the error -- in an application-specific manner.
  5. ...be added to existing code, so any exceptions thrown must be readily caught and understood.
  6. ...not be unduly expensive in terms of memory or performance, and the programmer will have the ability to specify which, if any, validations persist in production code.
  7. ...be useful for many validation tasks -- for example, in addition to supporting DBC, it must also allow a programmer to enforce constraints on a value without raising an error.
  8. ...respect namespaces and other C++ scoping mechanisms.

Such requirements are adequate creating a small library, which I'll be calling Validator. The entire code base is contained in a single header file, validator.h; I also created a test program, valtest.cpp.

Design & Implementation

Given those requirements, I went with a function-based approach to avoid incurring the overhead of "validation objects." All I need is a succinct tool for saying "if something isn't true, throw an exception." From that stems (obviously!), an exception class and a set of functions to perform validation tests. Another set of functions provide value enforcement, and well-named macros provide compile-time control over validation inclusion.

Note: For simplicity and to avoid duplicated text, I've removed most of the doxygen comments from the following code fragments. You'll probably want to look at validator.h for the full file, including namespace declarations.

Validator contains four "pieces", organized under the libcoyote namespace.

    template <typename Type>
    class validation_error : public runtime_error
    {
    private:
        static string build_error_string(const Type & object,
                                         const string & details)
        {
            stringstream message;

            message << "validation error: "
                    << typeid(object).name() << " " << object
                    << details;

            return message.str();
        }

    public:
        validation_error(const Type & object,
                         const string & details = string())
            : runtime_error(build_error_string(object,details))
        {
            // nada
        }
    };
    //! Validates that an object has a specific value.
    template <typename Type>
    void validate_equals(const Type & object,
                         const Type & constraint,
                         const string & message = string())
    {
        if (object != constraint)
        {
            stringstream details;
            details << " must equal " << constraint << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    
    //! Validates that an object does not have a specific value.
    template <typename Type>
    void validate_not(const Type & object,
                      const Type & constraint,
                      const string & message = string())
    {
        if (object == constraint)
        {
            stringstream details;
            details << " must not equal " << constraint << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    
    //! Validates that an object is less than <i>constraint</i>.
    template <typename Type>
    void validate_less(const Type & object,
                      const Type & constraint,
                      const string & message = string())
    {
        if (object >= constraint)
        {
            stringstream details;
            details << " must be less than " << constraint << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    
    //! Validates that an object is greater than <i>constraint</i>.
    template <typename Type>
    void validate_greater(const Type & object,
                      const Type & constraint,
                      const string & message = string())
    {
        if (object <= constraint)
        {
            stringstream details;
            details << " must be greater than " << constraint << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    
    //! Validates that an object has a value in a specified range.
    template <typename Type>
    void validate_range(const Type & object,
                        const Type & low_bound,
                        const Type & high_bound,
                        const string & message = string())
    {
        if ((object < low_bound) || (object > high_bound))
        {
            stringstream details;
            details << " must be between " << low_bound << " and "
                    << high_bound << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    
    //! Validates an object with a given predicate.
    template <typename Type, typename Predicate>
    void validate_with(const Type & object,
                       const Predicate & constraint,
                       const string & message = string())
    {
        if (!constraint(object))
        {
            stringstream details;
            details << " failed test " << typeid(constraint).name() << " " << message;
            throw validation_error<Type>(object,details.str());
        }
    }
    //! Enforce a lower limit on the value of an object.
    template <typename Type>
    void enforce_lower_limit(Type & object,
                             const Type & low_value)
    {
        if (object < low_value)
            object = low_value;
    }
    
    //! Enforce an upper limit on the value of an object.
    template <typename Type>
    void enforce_upper_limit(Type & object,
                            const Type & high_value)
    {
        if (object > high_value)
            object = high_value;
    }

    //! Enforce an range limit on the value of an object.
    template <typename Type>
    void enforce_range(Type & object,
                       const Type & low_value,
                       const Type & high_value)
    {
        if (object < low_value)
            object = low_value;
        else if (object > high_value)
            object = high_value;
    }
    // These macros allow validation to be included on a per-compile basis, based on the settings
    // of the DEBUG and NDEBUG preprocessor macros.
    #if defined(_DEBUG) && !defined(NDEBUG)
    #define LIBCOYOTE_VALIDATE_EQUALS(object,constraint,details) LibCoyote::validate_equals(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_NOT(object,constraint,details) LibCoyote::validate_not(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_LESS(object,constraint,details) LibCoyote::validate_less(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_GREATER(object,constraint,details) LibCoyote::validate_greater(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_RANGE(object,low_bound,high_bound,details) LibCoyote::validate_range(object,low_bound,high_bound,details)
    #define LIBCOYOTE_VALIDATE_WITH(object,constraint,details) LibCoyote::validate_with(object,constraint,details)
    #define LIBCOYOTE_LOCATION LibCoyote::build_location_string(__FILE__,__LINE__)
    #else
    #define LIBCOYOTE_VALIDATE_EQUALS(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_NOT(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_LESS(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_GREATER(object,constraint,details)
    #define LIBCOYOTE_VALIDATE_RANGE(object,low_bound,high_bound,details)
    #define LIBCOYOTE_VALIDATE_WITH(object,constraint,details)
    #define LIBCOYOTE_LOCATION ""
    #endif

 

    //! Utility function to create a location string.
    string build_location_string(const char * filename, const long line_no)
    {
        stringstream text;
        text << "in " << filename << ", line " << line_no;
        return text.str();
    }

How you use the facilities of validator.h depends on your application. The validate functions should not be used in a program where code size is critical, such as in an embedded system. And you need to carefully choose when to use the "magic" macros, and when you want validation to occur even in production code.

Usage

The valtest.cpp program provides a simple command-line unit test for validator.h, as well as examples of how the various functions and macros work.  A more "real world" example will be presented in my forthcoming static array class, where I use the the validation macros in certain functions to provide optional runtime range checking on indexes:

#if defined(LIBCOYOTE_BOUNDS_CHECKING)
#include <stdexcept>
#include <sstream>
#define LIBCOYOTE_ARRAY_EXCEPTIONS
#define LIBCOYOTE_ARRAY_CHECK_INDEX(n) validate_less(n,m_length,LIBCOYOTE_LOCATION);
#else
#define LIBCOYOTE_ARRAY_EXCEPTIONS throw()
#define LIBCOYOTE_ARRAY_CHECK_INDEX(n)
#endif

//...
// lots of code cut for this example
//...

// element access
template <typename Type>
inline Type & array<Type>::operator [] (size_t n) LIBCOYOTE_ARRAY_EXCEPTIONS
{
    LIBCOYOTE_ARRAY_CHECK_INDEX(n) // not here unless LIBCOYOTE_BOUNDS_CHECKING is defined
    return m_array[n];
}

template <typename Type>
inline Type array<Type>::operator [] (size_t n) const LIBCOYOTE_ARRAY_EXCEPTIONS
{
    LIBCOYOTE_ARRAY_CHECK_INDEX(n) // not here unless LIBCOYOTE_BOUNDS_CHECKING is defined
    return m_array[n];
}

As always, I'm interested in your comments.

- Scott




E-mail
LinkedIn
Facebook

•• HIRE SCOTT ••

Computer Books
Fiction
Articles
Reviews

FAQ
Bibliography


Link to Scott Ladd's Syraqua site

© 2010
Scott Robert Ladd
All rights reserved.
Established 1996


The grey-and-purple dragon logo, the blue coyote logo, Coyote Gulch Productions, Itzam, Evocosm, and Acovea are all Trademarks of Scott Robert Ladd.

Privacy Policy
Legal Stuff