Access:

» Beyond assert: Design by Contract for C++

Related categories: C/C++ | Object-oriented Technics

Scott Robert Ladd
Viewed: 3788 | Article date: 2006-07-18 14:31:07

Design by Contract (DBC) is a 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. This article describes concepts inherited from C, then move on to object-oriented solutions built with C++ for broader application.

Design by Contract (DBC) is a 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. The truths come in three flavours:

  • preconditions - specify what must be true upon entry to a function,

  • post-conditions - define what must be true when a function successfully returns,

  • invariants - declare what must be true about an object at all times.

About the author

Scott Robert Ladd is a 25-year veteran of software engineering and author of more than a dozen books about C++, Java and advanced computing techniques. He operates a consulting company, Coyote Gulch Productions (http://www.coyotegulch.com). Contact with the author: scott.ladd@coyotegulch.com

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. However, that doesn’t mean C++ is incapable of DBC. In building a basic DBC facility for C++, I’ll start with concepts inherited from C, then move on to object-oriented solutions built with C++ for broader application.

The assert() macro

When validating function arguments or resource allocations, many C++ programmers either use the standard C assert() macro (defined in assert.h or cassert) or they employ a platform-specific variant thereof. Listing 1 presents a typical use of assert(), watching for a null pointer in a simple string function.

Listing 1. Using assert() to check for a null pointer

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

// Do something with the 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 disable assert() by defining the NDEBUG symbol during preprocessing - doing so defines assert() as a null statement, effectively 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: when assert() calls abort(), your program stops. Destructors may or may not be called, and the C and C++ standards do not guarantee that buffers will be flushed or files closed. While this might be acceptable behaviour in small command-line utilities, users will quickly come to hate (and will stop buying) a complex application that terminates every time something goes wrong. This also renders assert() useless for performing any non-fatal validations, such as checking user input - unless, of course, you want your program to die every time someone hits the wrong key!

If you can live with the use of abort(), consider that assert() disappears in production code, which is usually built with NDEBUG set. Removing validation code is a Bad Thing, given that code spends far more time running in production than it does in debug mode. Testing code only goes that far, so when something bad happens in a live application, it must gracefully find a way to fix the issue or notify the user of problems. Do you honestly believe your development code is so good that it no longer needs to verify itself when it goes live?

Also consider that failing an assertion need not call for program termination. Imagine a parameter value falls outside a specified range: is it better to stop your program or simply fix the problem by modifying the value so it is acceptable? If you use assert(), you're limited to program termination.

As a final reason not to use assert(), bear in mind that it is a macro, so it does not recognize namespaces or other C++ scoping mechanisms. Reliable code should always use several techniques to avoid runtime errors, and in most cases assert() is the least desirable choice (though of course more desirable than omitting error checks altogether).

Controlling resource allocation

One way to avoid resource problems is to use an idiom known as Resource Allocation Is Initialization, or RAII. This 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, while the destructor performs clean-up and releases the said resource. Listing 2 presents an example of RAII in action.

Listing 2. Basic example of RAII resource management

class RIAAExample
{
public:
// Constructor
RIAAExample()
{
// Allocate memory
m_pointer = new int;

// Set value
m_pointer = 0;
}

// Do something with the 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 finalisation, where you explicitly control the lifetime of an object. The compiler keeps track of when your code destroys an object - either when the object passes out of scope or through a call to delete. In both cases, the destructor will always be called at a specific execution point, guaranteeing resource releases. A well-written C++ program will wrap all resource allocation in classes, eliminating many common and costly program errors.

Java, C#, and Visual Basic.NET do not support deterministic finalisation. Even if you explicitly destroy an object, it will not actually be destroyed until the automatic garbage collector kicks in. Sure, you can put code in a Java class’s finalize() method - but then 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 in an effort 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. Well, this alleged advantage loses much of its value when you have to 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-crafted Release() or Destroy() function any better than using the oft-maligned delete?

I prefer the C++ model of resource management through RAII. Of course, 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.

A d v e r t i s e m e n t
Linux BSD Unix ranking vote

Page: 1 2 3
Buy article Buy subscription
Buy now add to cart
add to cart
Standard price: 2€/$3 Standard price: 25€/$30
Buy article for as little as (2€/$3) each allow access to individual articles. Buy a full access to our Software Developers's Journal archive portal. You will be able to read the articles from all archive issues from year 2005 and 2006. For just 25€/$30 you get unrestricted access to the entire website for the whole year.
SDJhakin9

.SDJ Users:


.:Login
.:Password

[Register]
[Forgotten your password?]

...Shopping Cart

sum: 0 €
Choose currency:

...Topics

...Advertisement

www.acunetix.com www.verifysoft.com

...Conferences




...Print Edition Archive

...Affiliate Program



 

 

Subscribe | Contact Us | Newsletter | Privacy policy | Regulations | See all issues | About SDJ
Copyright C 2006 by Software Developer's Journal. All rights reserved.