Enforcing function call order in C++






2.17/5 (11 votes)
Sep 29, 2003
3 min read

64712

413
A simple set of macros to enforce function call order in C/C++.
Introduction
When declaring an interface (as a class for example), there is a need to define the allowed calling order of functions. For example, Init()
must be called before any other function, and nothing should be called after Shutdown()
.
In the spirit of design by contract, I wrote this tiny class + macros to make such rules enforceable in runtime.
See also:
This code was tested on VC 6 and should work on any C++ compiler.
Background
Design By Contract (DBC) is an issue that many people talk about but I did not see much practical work in the environment I work with (C++). DBC is all about clearly defining what your code should do (This might not be the best definition, but it will do for now).
While working on a complex project I found a bug caused by forgetting to call Init()
before the function I needed. Of course, it happened only in certain control flows, to make it more challenging.
The solution: Check in runtime that things happen as you expect them to be. Putting ASSERT()
s helps but it is not enough by itself.
Using the code
The code consists of one class and a few macros to make the source readable. For example, the statement function B::foo() should be called after Init() and not after shutdown()
is written as:
void B::foo() { Enter("foo"); Require(Called("Init")); Require(!Called("shutdown")); // do something here }
The full list of macros you can use (see also the .h file):
Enter(name)
- call this as the first thing in your function, unless this is your Init()
.
Called(name)
- return true
if function name
was already called (anytime in the past).
FirstCall
- return true
if no functions were called, including this function.
Prev(name)
return true
if name
was called just before the current function.
Require(boolean expression)
will blow a fuse if expression evaluates to false
. Currently it is simply ASSERT()
.
To use the code, include DBC_EnforceOrder.h and in the class declaration add a macro:
class B { // ... private: DBC_Data; // add this macro to your data declaration section };
In each function that may influence the state of "what is allowed to be called next", you have to add the Enter(name)
macro as in the B::foo()
.
Finally, to turn it off, make sure the macro _EnforceCallOrder
is undefined. Currently it is defined in the H file.
Drawbacks
- In every function (of interest) you must add
' Enter("function_name")
. In another article I wrote, there is a way around it using symbol table engine - but this complicates the code enormously. - I could add special rules such as
LAST_FUNCTION
, and then check for this status in theEnter()
call. I chose not to do it for the tradeoff between simplicity and elegance.
Future additions
- So far, only
REQUIRE
(i.e. precondition) is implemented. It would be nice to addENSURE
for post condition checking as well. - call
STACK_TRACE()
fromEnter()
to have the full monti.
Points of interest
I wrote this code after failing to find in the Internet, the exact solution to what I needed. There are plenty of articles on DBC, but not so much frameworks to work with. And remember the KISS principle!
History
- 2003-09-25: First release