ABS++ : Assertion Based Subtyping in C++
Herbert Toth, SIEMENS AG Austria, PSE KB B
|
 |
| |
REFEREED
ARTICLE |
Macro
packages
|

PDF Version |
|
Abstract
For software engineering to meet today’s challenges, well designed
reuse and com-position mechanisms must be established in both theory
and practice. Starting roughly ten years ago, theoretical principles
and solution possibilities for assertion annotations in daily practice
are being discussed in an ever growing number of papers and web pages.
We present a further proposal for C++ that is based on the macro technique,
conforms to the LSP, enables quantification, and very much resembles
the way Design by ContractTM is implemented in Eiffel.
1 INTRODUCTION
In this paper I present a C++ macro package together with a technique
that - while keeping the additional burden for the software engineer
as small as possible - offer him much of the features of Design by
ContractTM (henceforth: DbC) as provided by Eiffel (see e.g. [Meyer97]),
but avoid its deficiencies (see [Toth05]).
Though there are new ideas included in the macro packages, none of
them would ever have been conceived without the inspiration and without
the foundations laid by other people. What you find in this paper
is what my modest C++ and time resources allowed me to provide using
existing
concepts, code and visions. However, with some exceptions, I will
not undergo the labor to indicate sources explicitly: All of what is
listed
in the reference section has contributed in one way or the other
(and perhaps some other sources too that I do not remember any more).
The structure of the paper is as follows: Section 2 contains an overview
of the main concepts and rationales behind assertion based software
development and DbC. Section 3 describes the logical and structural
properties of ABS++, and in section 4 I present some technical
details of the ABS++ and the demonstration example. The final section
provides
a summary of the main ideas and some conclusions on the technique
presented in this paper. At the same location as the paper itself
you can also
find the two macro packages1 I
have used as well as the example sources. A number of hints to
further information is provided by references
to papers, books, and web pages.
2 ASSERTIONS AND DESIGN BY CONTRACT TM
What has been common in the area of hardware design for apoximately
twenty-five years under the name design for testability, viz. to
enhance the product with means to increase the observability of its
behavior, only during the last few years seems to gain some attention
also within software industry. This is all the more strange, as software
has gained a still rapidly increasing part of responsibility for
the well functioning of so many (really or only seemingly) important
things of every day life. Due to this pervasive part of software
in modern society, its quality is important not only in the context
of safety critical systems (e.g. nuclear power plants, cardiac pacemakers),
but also for our daily life as a whole, from business to leisure
(online banking, mobile phones, internet, cars etc.).
The technical means for achieving design for testability in software
engineering is assertion based programming, the logical strategy behind
is best known as DbC, a prominent and systematic method introduced
by Bertrand Meyer, the creator of the Eiffel language.
What certainly should be regarded as indication of an increasing
interest in this kind of software development starting in the mid-nineties
is
the growing number of publications in this area like e.g.:
- a considerable number of papers dealing with the concepts
and the practical use of assertions in general, e.g. [Binder00, chapter
17], [Mannion98],
[Meyer92], [Meyer97, chapter11], [Mitchell02], or [Payne97];
- the Assertion Definition Language (ADL) developed at Sun
Labs (see [ADL]);
-
newer Methods like Syntropy [Cook94], the Eiffel-related BON [Walden95]
and Catalysis [D’Souza99], provide means for the inclusion
of assertions into the graphical model;
- the Object Constraint Language (OCL) becoming a supplement
to the original version of the Unified Modeling Language (UML). Making
OCL a part of
UML is a consequence of the fact that graphical models alone are
not enough for a precise and unambiguous specification. There often
is
a need to describe additional constraints about the objects in
the model.
- emerging support for assertions for Java (from which even
the ANSI C assert mechanism has been removed at first, and reintroduced
later
on); see [Payne98], the iContract tool [Kramer98], Jass [Jass],
or AssertMate from Reliable Software Technologies [AssertM];
- emerging support for assertions for C/C++; see the overview
in [Maley00], [Binder99], [Porat95], or [Welch98].
Assertion based programming
Although I assume many of the readers to be familiar with the topic
under discussion, there will hopefullly also be some newcomers,
and it is for them that I provide a short summary about the main concepts
and ideas behind assertion based programming.
Software assertions are Boolean expressions that define the correct
state of a program at a particular location in the code. It is useful
to think of them as a kind of watchdog having abilities which can be
very helpful for you - if you are willing to provide them and only
when you allow them to be active. They can check method calls for proper
invocation, method code for correct computation, states of objects
for consistency, and also individual statements for errors. Therefore,
assertions may act in the following different roles, where the first
three groups form what I call contracting assertions:
- Preconditions express the requirements
that clients must satisfy whenever they call a routine, and are therefore
evaluated at the entry point
of a method. Preconditions are obligations for the client (caller),
and benefits for the server (callee).
-
Postconditions inform about
what the supplier (i.e. the routine) guarantees on return if the
precondition was satisfied on entry. They have to
be evaluated at all exit points of the method - if you allow more
than one in your coding standard. Postconditions are obligations
for the
server, and benefits for the client.
- In object-oriented projects: Class Invariants define
the consistency conditions for the state space of a class and must
be satisfied by
every instance of the class whenever this instance is externally
accessible, i.e. after creation, and after any call to an exported
routine. Class
invariants have to be evaluated at the entry and all exit points
of all externally visible routines of a class.
-
Data assertions define the
conditions that must hold at just their location in the code, whence
they are evaluated only at just this location.
You can take them for your own individually tailored and eventual
only temporary tests. A special case of data assertions are loop
invariants
and loop variants.
Using formal languages and methods [BowenFM] to overcome the problems
of software development outside of some safety-critical software systems
is far from getting the rule: This kind of formally deduced correctness
is often argued to be too difficult for the average system modeler
and thus has the great disadvantage that it can be applied by people
with a strong mathematical background only. Thus, a steep learning
curve and a big initial effort is to be expected for becoming familiar
with it.
Interesting alternatives in the area of formal languages and methods
to overcome this gap between what is possible in theory and what is
feasible in practice are e.g. the B-Language [Abrial96], [Escher] or
[Liu04] which allow specifications in a rather programming-oriented
style. Another possibility that seems to be more realistic nowadays
is assertion based programming according to DbC principles.
As you might know, all these ideas are not really new: their roots
date back at least to the late 60’s and the early 70’s.
There is indeed a long and tedious way in formal methods from Floyd’s
assigning meanings to programs in [Floyd67], Hoare’s axiomatic
basis [Hoare69] to Abrial’s Assigning programs to meaning subtitle
of The B-Book [Abrial96]. Today’s average software engineer usually
does not make use, and often does not even know, of formal methods
and corresponding tools. However, I think it is high time to take advantage
of some of their findings, and assertion based software engineering
may eventually prove to be a bridge between current practice and theory.
What is Design by ContractTM?
“Design by Contract” denotes a software development style
which (1) emphasises the importance of formal specifications, and (2)
interleaves
them with actual code. DbC is a systematic method of assertion usage
and interpretation introduced by Bertrand Meyer as a standard feature
of the Eiffel language. Without it, no trial would have ever been
made to provide a similar mechanism in other languages and, by no means,
would we have discussion papers like this and the ones mentioned
in
the references.
A software contract is the specification of the behavior of a class
and its associated methods. The contract outlines the responsibilities
of both the caller and the method being called. Failure to meet any
of the responsibilities stated in the contract results in a breach
of the contract, and indicates the existence of a bug somewhere in
the design or implementation of the software or - one must not forget
- in the assertions themselves. Software contracts can be completely
specified by preconditions, postconditions, and class invariants.
Software construction is based on contracts between clients and suppliers.
Each party expects some benefits from the contract, and accepts some
obligations in return. As in human affairs, the contract document
spells out these mutual benefits and obligations and protects both
he client,
by specifying how much should be done, and the supplier, by stating
that the supplier is not liable for failing to carry out tasks outside
of the specified scope.
DbC is, in a way, the opposite of defensive programming, a method
which recommends to protect every software module by as many checks
as possible.
This may result in redundancy and makes it also difficult to precisely
assign responsibilities among modules. Software contracts are also
a necessary prerequisite for introducing a notion of correctness:
If you do not state what your program should do, you are lacking
the norm
to which to compare what your program does in reality.
Contracts and inheritance
As the software community has learned during the last two decades,
the object-oriented approach is very powerful for developing large
software systems. Much of this power is due to the key concept of
inheritance. However, the statically checks enforced by e.g. C++
or Java compilers
upon derived classes test for such syntactic and typing restrictions
only that guarantee the lack of runtime type errors. This is the
contracting and specification level that has been used for too many
years in the
past by most software developers. Obviously, however, this is not
enough to prevent surpising and often disastrous behavior of programs.
In other words, the checks done by compilers are only part of what
is needed to reason about the behavior (i.e. the semantics) of software,
especially for object-oriented systems when new subtypes are added.
Behavioral subtyping is a technique for pre-venting unexpected behavior
in a modular way: it ensures that objects of new subtypes (instances
of subclasses) “act like” objects of their supertypes,
when used as if they were supertype objects. This is what the Liskov
Substitution Principle (LSP) for object-oriented design states [Liskov88]:
- In class hierarchies, it should be possible to treat a specialized
object as if it were a base class object.
- Or: Object-oriented functions that use pointers or references
to a base class must be able to use objects of a derived class
without knowing it.
The basic idea here is as simple as it is important: Derived classes
should not perform any actions that will invalidate the assumptions
made by (the client of) a parent class. Put differently, any object
of a subtype must be substitutable for an object of a supertype in
the hierarchy without any effect on the program’s observable
behavior. If the Liskov Substitution Principle is followed, code
using a base class pointer will never break after another class has
been
added to the inheritance tree.
This gives the basic rule governing the relationship between inheritance
and assertions: A descendant class must obey all the ancestors’ constraints,
i.e. routine pre- and postconditions, and the class invariants still
apply. Therefore, contracting assertions have to be checked along
the class hierarchy in a suitable way, i.e. they are used not only
on the
places where they appear in code (contrary to data assertions which
have local impact only). The assertions of a routine specify a range
of acceptable behaviors for this routine and its eventual redefinitions
which may specialize this range, but not violate it.
Data assertions
Data assertions do not contribute to software contracts as outlined
above, but rather are means to help doing some internal checks according
to your specific, and usually only temporary, needs. They define
conditions that must hold in a particular location in the code, and
are, therefore,
evaluated at this location only. As an exmaple we take a short look
on loop variants and invariants.
The Eiffel mechanism is described in [Rist95], p.298, as follows: "If
loop assertions are monitored at run-time, then both the variant and
the invariant will be evaluated immediately after loop initialization.
The invariant must evaluate to true, and the variant must be greater
than or equal to zero. If either of these conditions is not met, then
an exception will be raised and the system will terminate. As loop
execution continues, both the variant and the invariant will be evaluated
after each iteration of the body. As before, the invariant must be
true and the variant must be non-negative or an exception will be generated.
The system will also check that the variant is decreased by each execution
of the loop body."
How much assertion monitoring?
This section summarizes the considerations of an equally headed one
in [Meyer97], pp. 394-398, often using direct quotations from there.
What level of assertion tracing to enable at runtime "is a tradeoff
between the following considerations: How much you trust the correctness
of your software; how crucial it is to get the utmost efficiency; how
serious the consequences of an undetected run-time error can be." The
two extreme cases are
- to enable assertion monitoring at the highest level for
the classes of the system during testing it prior to release;
- to remove all monitoring for fully trusted systems in a
runtime-critical application area where every microsecond counts.
Note, however, the following remark by C.A.R. Hoare [Hoare73]: "It
is absurd to make elaborate security checks on debugging runs, when
no trust is put in the results, and then remove them in the production
runs, when an erroneous result could be extensive or disastrous. What
would we think of a sailing enthusiast who wears his life-jacket when
training on dry land but takes it off as soon as he goes to sea?"
“An interesting possibility is the option that only checks preconditions
... it has the advantage of avoiding catastrophes that would result
from undetected calls to routines outside of their requirements,
while costing significantly less in run-time overhead than options
that also
check postconditions and invariants.…
This option is particularly interesting for libraries. Remember the
basic rule on assertion violation: a violated precondition indicates
an error in the client; a violated postcondition or invariant indicates
an error in the supplier. ... But even for a perfect library it is
useful to check preconditions: the goal is to find errors in client
software.”
One cannot give a general answer to the question of how much assertion
monitoring to do at runtime without having some rough ideas about
the performance overhead caused by it. As a rule of thumb one can
take
the following: Preconditions are often relatively simple conditions
checking for the client's obligations like lower<= idx && idx <=
upper etc., whereas many postconditions and invariants may include
a lot of relevant consistency conditions expressing more advanced
semantic properties; moreover, invariants are evaluated twice. Thus,
the above
advice to adhere to Eiffel's default level of checking the preconditions
seems to be a practicable one for many situations.
3 THE ABS++ SOLUTION
An obvious and important fact you always have to keep in mind is
the following: Contrary to Eiffel compilers, C++ compilers do not
support
software development according to the DbC paradigm. As a consequence,
you as the developer have to support the compiler, i.e. you alone
are responsible not only for the correct implementation, but also
for the
correct placement of pre- and postconditions (with the exception
of class invariants), of the check invocations, and of the loop variants
and invariants. Again, following the schematic way as proposed in
the
ABS++ examples in this paper, this is an easy and systematic task.
In case you now have the feeling that, by using ABS++, you are forced
to write a lot of additional code, remember the following: Whether
you use Eiffel or some annotations in form of e.g. Java pseudocomments
together with some preprocessing mechanism, in any case you will
have to formulate and write down your assertions – in a both
syntactical and semantical correct way. What Eiffel or an intelligent
preprocessing
mechanism will save you, is the work you have with getting assertions
handled right over class hierarchies. This can, however, be done
in a simple and schematic way using the macros provided in QSubtype.h
(see section 4).
The theoretical background of ABS++
In this section we will give a short overview on the theoretical
background of ABS++ in order to give the reader the possibility to
compare our
macro package with related work. Our presentation is mainly based
on the results in [Chen00] and [Toth05].
Behavioral subtyping in ABS++ is done by the so-called weak plugin specification
match: (Cpre SCpre)
Cpost).
This is not a most general reuse ensuring2 match (see Theorem 7 in [Chen00]),
but it is not far from it as has been shown in [Toth05]. It can thus
be considered as a well-suited candidate for practical purposes.
Compared to the percolation pattern – which is the one used
in Eiffel and all the other DbC implementations for C++ or Java I know
(Jass excluded!) – there are two definitive advantages of the
ABS++ mechanisms: (1) hierarchy errors are detected for both pre-
and post-conditions, as well as class invariants; (2) never will
a routine
with its own precondition evaluating to false be
executed. (Suppose that for a method m (SCpre fails
and Cpre holds; according tho the percolation
pattern the effective precondition (i.e. what is really
checked at
runtime) for m in SC is SCpre true. Thus the execution of method m of class SC
will start notwithstanding the fact
that its own precondition SCpre evaluates to false, and that there
is an undetected hierarchy error from class C to class SC with respect
to m violating the Liskov Substition principle.)
It is important to know at what points in the program execution different
assertions have to be checked. The following table shows how pre-,
postconditions and class invariants are used in the context of
a class.
| |
|
Invariant |
Pre-condition |
Post-condition |
| Method Entry |
Instance Method |
yes |
yes1 |
- |
| Constructor |
no |
- |
- |
| Private Method |
no |
yes4 |
- |
| Static Method |
no |
yes |
- |
| Method Exit |
Instance Method |
yes2 |
- |
yes |
| Constructor |
yes3 |
- |
- |
| Private Method |
no |
- |
yes4 |
| Static Method |
no |
- |
yes |
- The preconditions of a method are checked only
in case that the class invariant has been found valid.
- The class invariant is checked at the end of a method only
if its postcondition has been found valid.
- You have to insert the classInvariant(_FL_) calls
manually into constructors. This means that you have to provide at
least one in order
to avoid C++’s default constructor.
- You may think of doing these checks (which are not ‘official’ contracts
for external clients of the class) using the check mechanism,
in order to clearly differentiate between contracting and other constraints.
Down-calling or How to manage visibility
In this section we shortly present the technique of down-calling
as proposed in [Payne97]. Down-calling can be used to ensure the semantic
consistency of method inter-faces for polymorphic methods.
Polymorphic methods with public visibility can become problematic,
because clients may provide different implementations of both assertion
and application code in derived classes. Down-calling solves this problem
by structuring the polymorphic call so that a method's pre- and postconditions
are always evaluated consistently across all derivations of a class.
The technique uses two types of methods:
- Interface methods are
public but not polymorphic (whence a derived class will inherit them
directly in the base class form); they manage
the preconditions and postconditions for the class methods, as well
as the class invariant. These methods are thus responsible for ensuring
the logical semantics of a method across the class hierarchy.
-
Implementation methods are
not public but polymorphic (whence a derived class can adapt their
behavior to its specific needs) and provide the
implementation for a particular interface method. Since their visibility
is restricted to the inheritance tree, clients cannot introduce
improper redefinitions.
This technique finally results in the Template Method pattern (see
[Gamma95]) and looks like the following example:

The interface method of our example clearly is C::foo, structured
in three parts: fooPre checks foo's precondition (and class invariant),
fooImp contains the application
code for foo, and fooPost checks the methods postcondition (and class invariant).
The typical ABS++ pattern
By putting the contracts into the .h files, we can emulate something
which is
similar to Eiffel’s short form, but we will never get such a beautiful
and elegant layout. You can do one more step by using an IclassName.h file for
the client interface containing all public methods in addition to the usual className.h
(which then has to include the IclassName.h file) and className.cpp.
Combining this with the considerations of the foregoing section we
get a typical (empty) template including the class invariant and the
constraints for method
foo in e.g. C.h. Do carefully note which methods are virtual and which are
not, as well as which methods are public and which are not.

How to handle empty assertions
It may, of course, occur that you do not have specific constraints
of a certain kind for either a method or a class. The question, then,
arises what default values to assign to such “empty” pre-
or postconditions, or class invariants. Empty assertions are trivially
set to true, since no constraint means no restriction whatsoever.
Hence, for pre- and post-conditions as well as for class invariants
everything is okay – always, regardless of the concrete program
state.
Note: Do always provide the empty pre- and postconditions,
and the empty class invariants in order to enable proper subtype and
class
invariant checks! (The information on the class names is passed in
the corresponding macros.)
Contract Support Evaluation of ABS++
The criteria used for evaluation of contract support are taken more
or less literally from [Plösch02] and [Plösch04], and are
split into four groups as shown in the tables below. (Note: For simplicity
reasons the term 'system' is used for programming languages, programming
language extensions and programming systems.)
Basic assertion support (BAS) |
| BAS-1 (Basic assertions) |
| Does the system support basic assertion annotations in the implementation
of a method? |
yes |
| BAS-2 (Preconditions and Postconditions) |
| Does the system support preconditions? |
yes |
| Does the system support postconditions? |
yes |
| May assertion expressions access properties of a class? |
yes |
| Are properties of a class guaranteed to remain unchanged during
assertion checking? |
yes |
| May assertion expressions also contain method calls? |
yes |
| Is it guaranteed, that a method call does not produce any side
effects (especially changes of the state of the object)? |
yes1) |
| BAS-3 (Invariants) |
| Is it possible to formulate invariants? |
yes |
| Are the restrictions in formulating invariants the same as for
the formulation of preconditions or postconditions? |
yes |
1. Use const methods for pre- and postconditions and
class invariants, but be cautious using the check macro.
Advanced assertion support (AAS) |
| AAS-1 (Enhanced assertion expressions) |
| May assertions contain Boolean implications? |
yes |
| May postconditions access the original values of parameters,
i.e., the values at method entry? |
yes1) |
| May an arbtrary expression be evaluated at method entry? |
yes |
| AAS-2 (Operations on collections) |
| Does the system support assertion expressions on collections? |
yes2) |
| Is it guaranteed that collections remain immutable in assertion
expressions? |
yes3) |
| May universal quantifications be expressed in the expression
language? |
yes2) |
| May existential quantifications be expressed in the expression
language? |
yes3) |
| AAS-3 (Additional expressions) |
| Does the assertion expression language have additional features? |
no |
| Are these additional features guaranteed to be side effect free? |
no |
- You have to provide the values at entry by using
a macro.
- The mechanism works for iterators too; see the vector example
at the bgin of ABS.cpp.
- See note to table for Basic Assertion Support on page 11.
Support for Behavioral subtyping (SBS) |
| SBS-1 (Interfaces) |
| Is it possible to specify contracts for interfaces? |
(yes) |
| May contracts be added for classes implementing assertion-enriched
interfaces? |
- |
| SBS-2 (Correctness I) |
| Does the system impose any restrictions on subcontracts? |
yes |
| Does the system ensure that preconditions may only be weakened? |
yes |
| Does the system ensure that postconditions may only be strengthened? |
- |
| SBS-2 (Correctness II) |
| Does the system impose stronger requirements on subcontracts
as specified in SBS-2? |
yes1) |
| Does the system ensure, that the correctness rules for behavioral
subtyping [Liskov94] are not violated? |
yes |
- Does subtype checks for class invariants too (analogous
to postconditions).
Runtime monitoring of assertions (RMA) |
| RMA-1 (Contract violations) |
| Is an exception handling mechanism available in case of violations
of assertions? |
yes2) |
| Are there additional features available for dealing with assertion
violations (e.g., log files)? |
yes |
| RMA-2 (Configurability) |
| Is it possible to enable and disable precondition checking, postcondition
checking and invariant checking selectively? |
yes |
| Is it possible to enable and disable assertion checking on a
package, class or even method level? |
no3) |
| RMA-3 (Efficiency) |
| Are there any additional memory requirements even when assertion
checking is disabled? |
no |
| Is there any additional processor usage even when assertion checking
is disabled? |
no |
- Depends on how you define the assertAndReact macro
in QAssert.h. Alternatively, you may think of integrating exeception
handling in another way, e.g. as shown in section 5.
- Possible on class level; but does not make very much sense
in the light of proper subtype checking.
4 THE ABS++ MACRO PACKAGES AND SOME EXAMPLES
QAssert.h
This part of ABS++ is due to [Mueller94], with some slight modifications
and renamings. It is here where quantifiers are made possible. For
technical details we have to refer the reader to [Mueller94], but in
order to make the current paper a rather self contained piece of information
and to prevent any difficulties concerning the availability of the
October 1994 CUJ issue, we give some hints on how to use this macro
package.
- assertAndReact has the same effect as the normal assert, but additionally
it outputs all values given in the second parameter to a special output
stream, and thus it avoids the secrecy of assert about the values that
caused the assertion to fail. This is especially useful in connection
with the forall and exists conditions,
because knowing which cycle of the internal for loop failed is important
for tracing an error.
If you want to output the loop variable in case an assertion fails,
you must declare it at the global scope as shown in the examples of
in the subsequent section.
Both the output stream and the reaction on
errors can be changed simply by redefining the macros ASSERTSTREAM and REACT_ON_ERROR, respectively.
Note, however, that due to the definition of assertAndReact as an expression,
REACT_ON_ERROR must be an expression, too.
- There are two restrictions on forall and exists conditions: They cannot
be used as conditions in if statements, and they cannot be used inside
a parenthesized subexpression. To work around the following:

- you have to write:

- There may also arise the need for adapting the QAssert.h
macros to the specific needs of your project, e.g in the following
way:

QSubtype.h
The ABS++ macro package (in file QSubtype.h) provides some simple
macros that correspond to Eiffel’s DbC philosophy and approximate also its syntax.
It consists of two logical layers:
- The basic assertion layer defines the four kinds of clauses
that Eiffel provides for formulating constraints on methods, classes,
or statements:
- require for preconditions;
- ensure for postconditions;
-
invariant for class invariants; and
- check for non-contracting constraints.
Furthermore we use
-
LoopInvariant for loop invariants;
- InitVariant and LoopVariant for loop variants.
Via CONTRACT_LEVEL you
can define check levels similar to Eiffel:

In addition to and independent from this Eiffel-like
layering for contracting assertions, in ABS++ you can also define data
assertions using check macros which also provide different levels,
as well as constraints for loops.
Examples for the usage of the check feature can be found e.g. in
Anything.cpp or at the begin of ABS.cpp in conntection with the iterator
tests,
and for loops a little bit later in this paper. You have control over
these checking mechanisms if you e.g. #define CHECK_LEVEL
1 or #define
CHECK_LOOPS according to your needs.
- The subtype control layer macros enable you to write
- Preconditions (checkPreconditions),
- Postconditions (checkPostconditions, upwardsCheck),
- class invariants (checkInvariants, checkInvars), and
- subtype checks (checkPreSubtype, checkPreSubtype_P1, checkPreSubtype2,
checkPostSubtype, checkPostSubtype2, checkInvSubtype,
checkInvSubtype2) in a comfortable way.
Note, that you do not need to bother with the invariant checking
macros: they are implicitly used by the pre- and postcondition
checks. Do also
note that it is straightforward how to extend the subtype
checking macros to deal with more than two parent classes if you ever
will want to use multiple (not simple code inheritance but)
subtyping, or to
properly handle more than one method parameter according
to
the following scheme. (This is a stupid thing, but somehow
you must
provide the
intelligence that is otherwise included in tool algorithms.)

Where:
>= 2: number of direct parent classes in case of multiple subtyping,
and
>= 1: number of parameters.
Our general checking policy is as follows:
- Each of the checkXXX blocks can have several
clauses all of which we want to check in order to get as much error
information as possible.
Therefore, we do not stop at the first clause that has been found
to fail, but continue checking all clauses. So we need a checksOkay_ flag
for each of the checkXXX blocks for making the final decision if an
error has been detected in the sequence of its clauses.
- Each class with pre- or postconditions has to provide a
function with the name classInvariant,
which is called from both the checkPreXXX and checkPostXXX macros
in an appropriate way. This is the only thing you have to do for
class invariant checks.
- The preconditions of a method are checked only in case
that the class invariant has been found valid, and the class invariant
is checked
at the end of a method only if its postcondition has been found valid.
- Contrary to Eiffel, where non-contracting data assertions
can be activated only on top of the other three kinds of assertions
(i.e. only if require,
ensure, and invariant have already been switched on), ABS++ keeps
such data assertions via check completely independent from contracting
assertions
and also provides a separate level mechanism for them), in order
to eventually avoid runtime penalties.
- In postconditions it sometimes makes sense to compare the
new value of a variable with the old one, i.e. we want to check a
certain relationship x’ = f(x, …), with x’ denoting
the new value. Contrary to Eiffel, C++ does not provide a mechanism
that saves the old value
before the execution of a method’s body; therefore, it
is up to you as the programmer to do that. Using QSubtype.h you
can use the
old-mechanism in a very simple way as shown in the following
code (from C.hpp):


In ABS++ you can easily turn on and off (in part (1)
of Qsubtype.h)
- a trace of the assertion mechanism, either for debugging
this mechanism itself, or to gain a better understanding of it by
activating the appropriate
definition of the TRACE_CONTRACT macro;
- the check of invariants in preconditions (in case you are
sure that you need not check for the indirect invariant effect; see
[Meyer97]);
- the data assertions mentioned above using the check and
the CHECK_LEVEL,
and the CHECK_LOOPS macros mechanisms
as desired;
- the checking of the contracting assertions on three increasing
severity levels:
- (a): preconditions, (b): (a) + postconditions, and (c):
(b) + class invariants;
- on a per class basis all kinds of contracting assertions
by appropriately initializing the public static bool _ABSppChecksEnabled_ attribute.
In the light of subtype checking however, this is a possible but
not very meaningful strategy.
With ABS++ you can easily
- redirect the output of ABS++ via the ASSERTSTREAM macro in Qassert.h
(in the example I use cout or ABSppLog connected to a file in ABSppGlobals.h).
- adapt the REACT_ON_ERROR macro
in Qassert.h to your preferred error handling strategy by redefining
it to a call of a project specific
error handler (in the example I simply use exit(333) or WaitForEnterKey() which
does not immediately stop after an assertion violation, but allows
to continue, mainly for testing purposes of ABS++ itself.
- handle recursive function calls correctly: Due to the separation
of the contracts from the implementation, the recursion naturally
is in
the fooImp; so only the outermost call
is checked against the contract.
-
follow the “Don’t check the checker” rule: Just
call fooImp instead of foo.
This may eventually interfere with the visibility constraints if
called from outside the implementing class; if this
is the case, you might provide a suitable query for clients [Mitchell02,
ch. 2]. Remember: Contracts have to be side-effect free!
-
(A feature of a class, i.e. an attribute or a method, is either a query
(which yields information about but does not change the visible properties
of an object) or an command (which might change the object but does
not return a result). The idea is that queries do not need any precondition,
nor does it guarantuee anything: by definition it must not change an
object’s state.)
- start into an already running project: Doing things correctly,
there should be no troubles.
Some small examples
- We start with a small and simple example for loop checking
(the loop variant construction has been adapted from [Marin96]):

By placing the loop variant and invariant at the end
of the loop body you only loose their check after initialization. However,
it is reasonable to assume that a wrong initialization will have some
severe impacts on the calculation of the loop's body and will thus
not go undetected through the check at its end. Though you will not
get the exact Eiffel behavior in that way, you will be able to work
with a sufficiently close approximation to it.
- Here is an example for
the nested usage of quantifiers (but perhaps not necessarily for
clever C++ data structures) that checks if all
possible triples are there:
- Here is an
example that shows how subtype checks over the class hierarchy have
to be written for the precondition of method foo of
class SC which is a subclass of C,
using one parameter:

The demonstration example Let us have
a short look on how these principles are used in the attached example
files where you can find the main program together
with a
sample output for which the assertions have been configured in a
way to give a complete walk through. Thus you can trace how assertion
checking is done across the class hierarchies according to DbC and
subtype requirements.
 In order to facilitate understanding I have chosen a very simple
class hierarchy as follows: we have two base classes C and C1, with
SC inheriting
from C, and SSC from SC and C1, i.e. we work with multiple inheritance.
The consequence of this you can see in the implementation methods of
the class SSC, viz. SSC::ClassInvariant, SSC::fooPre, and SSC::fooPost,
where you have to check for both ancestor classes, i.e. C1 and SC.
Note that, while traversing across the class hierarchy is done in the
implementation methods, the invocation of the class invariants and
the checks for the instrumentation level is done in the Qsubtype.h
macros.
Do also be aware that the contents of the pre- and postconditions
and of the invariants in our example serve mainly for documentation
purposes.
The §-ed comment lines are bordering those parts of the program
text that deal with DbC stuff: Do not get fooled by their size compared
to the rest, but take into account that, practically, we do not have
any real code.
Scattered about the code there are lines marked with ‘failure#
# # # # # #’; they contain statements that cause the error as
indicated in the comment above it. The kind of error occurs only when
the activation of the corresponding statement is done starting from
the original source, i.e. assertion violations are – obviously
- not independent from each other. Eventually, you will thus have to
do a short play for producing them.
5 SUMMARY AND CONCLUSIONS
Following the DbC paradigm in the way as proposed here has some remarkable
advantages:
- You do not need a tool in addition to the C++ compiler
of your choice.
- You do not need to learn another language or pseudocomment
syntax: Following the method proposed in this paper you write normal
C/C++
code. The only thing you have to take care of is to place and type
things appropriately; but this can be done in an easy and schematic
way using the template routines of the example.
- You can use quantified assertions explicitly. Thus you
can translate predicate logic specifications of e.g. BON, Catalysis,
or Syntropy
(which you may have formulated in OCL, eventually) directly into
code.
- You gain seamlessness, i.e. it is possible to
use a single notation and a single set of concepts throughout the
software life cycle, from
analysis and design, to implementation and maintenance.
-
You may also overcome the programmer’s usual resistance against
what they feel excessive documentation requests burdened upon them
by management, an emotion that - we should frankly admit it - is
not unknown to most of us. Where does this come from? Rather sooner
than
later in the design phase, experienced software engineers tend
to write down syntax fragments instead of prose, because at some
point they
start to think mainly or also in programming language constructs.
Using assertions they can do just that, normally gaining a lot of
benefits
of both psychological (they feel good because they write down what
they can use immediately), but also of technical kind (they save
the error prone activity to translate everything from ambiguous natural-language
texts or UML diagrams to exact, but executable specifications in
a
programming language). And managers should not forget that, in
addition to the just mentioned advantages, software contracts also
can provide
documentation which is much more exact than just pure prose. As
always, a good mixture of the two will yield the best results.
- You can - and this is the most important of all the benefits
- apply a win-win strategy using assertion based software engineering
with
obvious advantages for both your customer and you as the developer
of a software system.
Applying the ideas underlying DbC can help us a lot on the way towards
correct and reliable software:
- Writing contracts from the very beginning enforces developers
to state what they are trying to do already during design. This definitely
helps
doing it right!
- Assertion based programming in general, and DbC as a systematic
and well defined variant of it especially, should be regarded as
built
in self tests and as a permanent online review that both help
in early error detection and give the software developers a reasonable
amount
of security about the correctness status of their programs.
- Contracts can, and usually should, also serve as a basis
for (technical) documentation, especially for an up-to-date description
of the public
interfaces.
-
Contracts enable the top developers to express the intent behind their
designs and hence to leave behind a clear statement of their original
concepts, reducing the risk that further contributors or maintainers
will destroy the software’s consistency and quality.
- Contracts are a useful testing and debugging tool: (i)
they save debugging time due to the improved observability, where
failures occur close
to the bugs; (ii) they express unambiguously what an author expects
and what (s)he guarantees in turn. So one has a clear statement
not only of What Is (in the implementation part) but also
of What
Should Be (in the assertions part). And only under such
circumstances one can definitely identify a discrepancy between
the two.; and (iii)
they help you in designing and performing your tests.
My main aim was to put emphasis on the benefits of assertion based
software development, as well as to provide a basic framework for
C++ to start with. As it is usual not only for a first version, this
framework
is open for extensions and improvements. Some major topics not handled
are:
-
a more sophisticated Anything class for saving the “old” values;
-
coding conventions for enforcing a ‘result style’, i.e.
the possibility to check the calculated result of a non-void method
immediately before it will be returned to the caller;
- considerations on the integration possibilities of a rescue
mechanism;
- a more explicit integration of an exception handling mechanism.
This, usually, depends on the project specific coding conventions.
ABS++
can eventually be adapted to your needs by a suitable definition
of the assertAndReact and REACT_ON_ERROR macros
in Qassert.h.
- As alternative consider the adaptation of a scheme like
the one below. But be aware that properly designing an application
for exception handling
is a topic of its own.
Footnote
1 Download the two macro packages
2 A specification match
M is reuse ensuring if M(SC,C) {Cpre}mSC{Cpost},
where {P}m{Q} represents a Hoare triple, informally stating
that method m if started in a state such that P holds does
stop in a state where Q holds.
REFERENCES
[Abrial96] J.-R. Abrial: The B-Book: Assigning
Programs To Meanings, Cambridge University Press,
1996.
[ADL] Assertion Definition Language (ADL) at
http://adl.opengroup.org/
[AssertM] AssertMate at http://www.cigital.com/
[Binder99] R. Binder, The Percolation Pattern – Techniques
for Implementing Design by Contract in C++, C++
Report, May 1999, 38-44.
[Binder00] R. Binder, Testing Object-Oriented
Systems: Models, Patterns, and Tools, Addison-Wesley,
2000.
[BowenFM] Formal Methods Home Page at http://www.afm.sbu.ac.uk/fm/
[Chen00] Y. Chen/B.H.C. Cheng, A Semantic Foundation
for Specification Matching. In G.T. Leavens and
M. Sitaraman (Eds.), Foundations of Component-Based
Systems, Cambridge Univ. Press, 2000, 91-109.
[Cook94] S. Cook/J. Daniels, Designing Object
Systems: Object-Oriented Modelling With Syntropy,
Prentice
Hall, 1994.
[D’Souza99] D. D’Souza/A.C. Wills,
Objects, Components, and Frameworks with UML.
The Catalysis Approach, Addison Wesley, 1999.
[Escher] Perfect Developer, at http://www.eschertech.com/index.php
[Floyd67] R.W. Floyd, Assigning meaning to programs.
In Proc. of Symposia in Applied Mathematics,
Mathematical aspects of computer science Vol. 19, American Mathematical
Society, 1967, 19-32.
[Gamma95] Gamma, E./Helm, R./Johnson, R./Vlissides,
J., Design Patterns, Addison Wesley, 1995.
[Hoare69] C.A.R. Hoare, An axiomatic basis for
computer programming, Communications of the
ACM,
Oct. 1969, 576-580.
[Hoare73] C.A.R. Hoare, Hints on Programming
Language Design, Stanford University Artificial
Intelligence
memo AIM224/STAN-CS-73-403. Reprinted in [Hoare89],
193-214.
[Hoare89] C.A.R. Hoare/C.B. Jones (Eds.), Essays
in Computing Science (reprints of Hoare's papers),
Prentice Hall, 1989.
[Jass] Jass at http://semantik.informatik.uni-oldenburg.de/~jass/
[Karaorman99] Karaorman, M./U. Hölzle/J.
Bruno, jContractor: A Reflective Java Library to
Support
Design By Contract, in: Proceedings of Meta-Level
Architectures and Reflection, 2nd International
Conference, Reflection '99. Saint-Malo, France.
Lecture Notes in Computer Science #1616, Springer
Verlag, 1999, pp. 175-196.
[Kramer98] R. Kramer, iContract - The JavaTM
Design by ContractTM (formerly available
at http://www.reliable-systems.com/tools/iContract/iContract.htm)
[Liskov88] Liskov, B., Data Abstraction and Hierarchy,
SIGPLAN Notices 23(5), May 1988.
[Liskov94] Liskov, B./Wing, J.M., A behavioral
notion of subtyping, ACM Transactions on Programming
Languages 16, 1811-1841, November 1994.
[Liu04] Liu, Shaoying, Formal Engineering
for Industrial Software Development. Using the
SOFL
Method, Springer, 2004
[Maley00] Maley, D. Mind the Gap – Formalising the Development of Scientific
Software. Ph.D. Theses, Queen’s University of Belfast, March 2000.
[Mannion98] Mannion,M./Philips,R., Prevention
Is Better Than A Cure, JavaTM Report, September
1998, 23-36.
[Marin96] Marin, M.A., Effective use of Assertions
in C++, ACM SIGPLAN Notices, Nov. 1996 (also available
at http://www.cs.rpi.edu/~wiseb/sigplan/issues/ctb9611.ps ).
[Meyer92] Meyer, B., Applying “Design by Contract”,
IEEE Computer 25(10), 40-51, October 1992.
[Meyer97] Meyer, B., Object oriented software
construction, 2nd Ed., Prentice Hall, 1997.
[Mitchell02] Mitchell, R./McKim, J., Design
by Contract, by Example, Addison-Wesley, 2002.
[Mueller94] Mueller, H.M., Powerful Assertions
for C++, C/C++ Users Journal, October 1994, pp.
21-37
[Payne97] Payne, J.E./Alexander, R.T./Hutchinson,
C.D., Design-for-Testability for Object-Oriented
Software, Object Magazine, July 1997, pp. 35-43.
[Payne98] Payne, J.E./Schatz, M.A./Schmid, M.N.,
Implementing Assertions for Java, Dr. Dobb’s
Journal, January 1998 (also available at http://www.ddj.com/ddj/1998/1998_01/payn/payn.htm )
[Plösch02] Plösch, R., Evaluation of
Assertion Support for the Java Programming Language,
Journal of Object Technology, vol. 1,
no. 3, special issue:
TOOLS USA 2002 proceedings, 2002, pp. 5-17 http://www.jot.fm/issues/issue_2002_08/article1.
[Plösch04] Plösch, R., Contracts,
Scenarios and Prototypes. An Integrated Approach
to High
Quality Software, Springer, 2004
[Porat95] Porat,S./Fertig, P., Class assertions
in C++, J. of Object-Oriented Programming, May
1995, pp. 30-37.
[Rist95] Rist, R./Terwillinger, R., Object-Oriented
Programming in Eiffel, Prentice Hall, 1995.
[Toth05] Toth, H., On theory and practice of
Assertion Based Software Development, in Journal
of Object Technology, vol. 4, no. 2, March-April
2005, pp. 109-129,
http://www.jot.fm/issues/issue_2005_03/article2
[Walden95] Walden, K./Nerson, J.-M., Seamless
Object-Oriented Software Architecture. Analysis
and design of reliable systems,
Prentice
Hall, 1995 (see http://www.bon-method.com )
[Welch98] Welch,D./Strong, S., An Exception-Based
Assertion Mechanism for C++, J. of Object-Oriented
Programming, July/August
1998,
pp. 50-60
About the author
| Herbert Toth is a senior software engineer
in the Program and System Engineering Department of SIEMENS AG
Austria. He holds a diploma degree in computer science from the
Technical University, and a Ph.D. degree in mathematical logic
from the University, both in Vienna. During the eighties and nineties
his research interests led to some publications on the foundations
of fuzzy set theory. He can be reached at mailto:herbert.toth@siemens.com. |
Cite this article as follows: Herbert Toth: “ABS++ : Assertion
Based Subtyping in C++”, in Journal of Object Technology, vol.
5, no. 6, July - August 2006, pp. 83-105 http://www.jot.fm/issues/issue_2006_07/article3
|