The header file should, just like in Modula-2, reveal as little as possible of the implementation. But in contrary to Modula-2, in C++ it's very hard to hide everything of the implementation, as we'll see later on.
An example of the interface of a stack as it could be defined in Modula-2:
DEFINITION MODULE IntStacks; (* Elements: Although the elements could be of a variety of types, for simplicity we assume that they are of type INTEGER *) (* Structure: Linear, Last in first out *) (* Domain: The number of elements in a stack is bounded to a certain maximum. *) TYPE IntStack; (* Opaque type to represent a Stack. *) PROCEDURE Push(VAR S: IntStack; e: INTEGER); (* pre - S is not full. post - S includes e as its most recently arrived element. *) PROCEDURE Pop(VAR S: IntStack; VAR e: INTEGER); (* pre - S is not empty. post - e is the most recently arrived element of S-pre; S no longer contains e. *) PROCEDURE Empty(S: IntStack): BOOLEAN; (* post - TRUE : S has no elements FALSE: S has at least one element *) PROCEDURE Full(S: IntStack): BOOLEAN; (* post - TRUE : S has reached its bound FALSE: S can at least contain one more element *) PROCEDURE Create(VAR S: IntStack): BOOLEAN; (* pre - S does not exist post - TRUE : S has been created, S is empty FALSE: there was not enough memory left to create S *) PROCEDURE Terminate(VAR S: IntStack); (* post - S-pre does not exist. *) END IntStacks.
A typical header file of the same ADT might look like
// intstack.h #ifndef _INTSTACK_H_ #define _INTSTACK_H_ class intstack { public: intstack(); // post - The stack has been created, the stack is empty ~intstack(); // post - The stack-pre does not exist void push(int e); // pre - The stack is not full. // post - The stack includes e as its most recently arrived element. void pop(int& e); // pre - The stack is not empty. // post - e is the most recently arrived element of the stack-pre; // the stack no longer contains e. bool empty(); // post - true : The stack has no elements. // false: The stack has at least one element. bool full(); // post - true : The stack has reached its bound. // false: The stack can at least contain one more element. private: int* stack_; int top_; }; #endif
The #ifndef
/#define
construction is used to prohibit multiple
inclusions of the header file.
The interfaces in Modula-2 and C++ are very similar, but they differ on some
important points. The most noticeable difference is the absence of a
parameter of the stack type in all member functions. This is because
functions do not work on an instance, but are requests to an
instance to perform some action. Compare calling the Full
function on
a variable in Modula-2:
: IF Full(MyStack) THEN :to asking a stack-instance if it's full:
: if (mystack.full()) :
Another difference is the overall structure of the interface. In Modula-2,
all functions are in fact seperate functions with one common parameter. In a
C++ class, the functions are all member functions of the ADT and are
therefore declared within the class intstack { ... };
statement.
The third difference is the exposure of the internal variables of a class.
The only thing known about the type IntStack
in the Modula-2 interface
is the fact that it exists; it's completely opaque. But in C++, all
variables used in the implementation have to be declared in the interface,
thereby making it virtually impossible to create an ADT that's really
abstract. Making these member variables private
will shield them
from being used directly by a user.
The last difference is the names of the functions to create and destroy
instances of the ADT. C++ offers constructors and destructors, so you should
not give them other names like Create
or Terminate
.