Previous | Table of Contents | Next |
6.2.4.7. The Protection Model
Before starting work on C with Classes, I worked with operating systems. The notions of protection from the Cambridge CAP computer and similar systemsrather than any work in programming languagesinspired the C++ protection mechanisms. The class is the unit of protection and the fundamental rule is that you cannot grant yourself access to a class; only the declarations placed in the class declaration (supposedly by its owner) can grant access. By default, all information is private.
Access is granted by declaring a function in the public part of a class declaration or by specifying a function or a class as a friend. Initially, only classes could be friends, thus granting access to all member functions of the friend class, but later it was found convenient to be able to grant access (friendship) to individual functions. In particular, it was found useful to be able to grant access to global functions.
A friendship declaration was seen as a mechanism similar to that of one protection domain granting a read/write capability to another.
Even in the first version of C with Classes, the protection model applied to base classes as well as members. Thus a class could be either publicly or privately derived from another. The private/public distinction for base classes predates by about five years the debate on implementation inheritance versus interface inheritance (Liskov, 1987; Snyder, 1986). If you want to inherit an implementation only, you use private derivation in C++. Public derivation gives users of the derived class access to the interface provided by the base class. Private derivation leaves the base as an implementation detail; even the public members of the private base class are inaccessible except through the interface explicitly provided for the derived class.
To provide semi-transparent scopes, a mechanism was provided to allow individual public names from a private base class to be made public (Stroustrup, 1982).
6.2.4.8. Runtime Guarantees
The access control mechanisms described previously simply prevent unauthorized access. A second kind of guarantee was provided by special member functions, such as constructors, that were recognized and implicitly invoked by the compiler. The idea was to allow the programmer to establish guarantees, sometimes called invariants, that other member function could rely on. For example, Classes: An Abstract Data Type Facility for the C Language (Stroustrup, 1980a) presented this refinement of class stack (section 6.2.4):
All stacks created using the definition above have the size SIZE. This is not ideal, so let us try again:
class stack { void new(short); void delete(void); char * min; char * top; char * max; public: void push(char); char pop(void); };This class stack declaration does not specify the amount of store to be allocated for the stack itself. Instead it is specified that an argument of type short must be provided for stack.new(). Arguments to a new() function are provided as part of the declaration of a class object. Heres an example:
class stack s1(SIZE), s2(200);The new() function then provides the interpretation of them:
void stack.new(int size) { top = min = new char[size]; max = min+size-1; }A vector of size characters is allocated on the free store. This, however, creates a new problem. Because we cannot (in general) assume that a garbage collector is available, we must clean up after ourselves. That is, in this case we must deallocate the vector pointed to by min when an object of class stack is deleted. This is done by defining a parameterless function called delete:
void stack.delete() { delete min; }If a function of this name is mentioned in the class declaration, it is guaranteed to be the last function accessing an object of that class before it is deleted.
The initial syntax for constructors and destructors was revised during the revision that yielded the initial C++ (section 6.3.3.1) so that the example looks like this:
class stack { char* min; char* top; char* max; public: stack(int); // constructor ~stack(); // destructor void push(char); char pop(); }; void stack::stack(int size) { top = min = new char[size]; // allocate space for elements max = min+size-1; } void stack::~stack() { delete[] min; // delete array of elements }
Constructors and destructors proved immensely valuable and are the basis for many C++ programming styles (for example, see section 6.5.6).
Curiously enough, the initial implementation of C with Classes contained a feature that is not provided by C++ but is often requested. In C with Classes, it was possible to define a function that would implicitly be called before every call of every member function (except the constructor) and another that would be implicitly called before every return from every member function. They were called call and return functions. They were used to provide synchronization for the monitor class in the original task library (Stroustrup, 1980b):
class monitor : object { /* ... */ call() { /* grab lock */ } return() { /* release lock */ } };
These are similar in intent to the CLOS :before and :after methods. Call and return functions were removed from the language because nobody (but me) used them and because I seemed to have completely failed to convince people that call() and return() had important uses. In 1987, Mike Tiemann suggested an alternative solution called wrappers (Tiemann, 1987), but at the USENIX implementors workshop in Estes Park, this idea was deemed to have too many problems to be accepted into C++.
Previous | Table of Contents | Next |