<<

Removing Details from C++ Class Declarations

Mark R. Headington Science Department University of Wisconsin-La Crosse La Crosse, WI 54601 [email protected]

Abstract that defines, for both the user and the implementor, the services provided by the ADT without reference to imple- Data abstraction= concept introduced at varying places in mentation details. The implementation encapsulates the the CS l/CS2/CS7 sequen~separates the properties of a concrete data representation and the program code for the (its values and operations) fmm the implementa- ADT operations. Client code cannot access encapsulated tion of that type. This separation of spxification fmm data except through the operations provided in the . implementation is achieved by encapsulating the implemen- This separation of specifkation tlom implementation yields tation so that users of the type can neither access nor be three important beneti~ influenced by the implementation details. Ideally, therefore, the specifkation should be implementation-independent, ● The user does not have to know how the abstraction The C++ class mechanism compromises information works in order to use it correctly, hiding by requiring the interface to include information-the ● The implementor is guaranteed that client code cannot private part of the class &claratio*that is needed only for compromise the correctness of the implementation. implementation pqoses. This paper describes two tech- ● Changes to the implementation do not require clients to niques for removing details of implementation structure be reprogrammed and recompiled. from the C+ class declaration and discusses the advantages and disadvantages of each, In C++, the private part of a class declaration can-and usually does-include the declarations of the 1. Introduction concrete data representation of an ADT

At some point in the CS1/CS2/CS7 sequence, data abstrac- #include “bool .h” / / Declarations for tion is introduced as a tool for managing complexity in // Boolean type systems. Data abstraction, the separation of the class Int Stack { properties of a data type from its implementation, depends public: heavily on encapsulation-the technique of minimizing Boolean IsEmpty ( ) Const; interdependencies among separately-mitten modules by Boolean IsFull ( ) const; defining strict external interfaces Nica88, Snyd86]. void Push ( int ) ; In object-oriented programming languages, data int Top ( ) const; void Popo; abstraction and encapsulation are provided by the class IntStack ( ) ; // Constructor mechanism. The class, a program representation of an private: (ADT), encapsulates both structure (the lnt data [MAX_DEPTH] ; concrete duta representatwn of the abstract data) and int top; behavior (algorithms that implement operations on the }; abstract data). Encapsulation follows because an ADT The disadvantage is that the interface represented by such defines only the abstract properties of the type, not the a class declaration is not implementation-independent. The concrete data representation or the of the declaration of Int Stack clearly discloses the data repre- associated operations, sentation- int array and a variable to keep track of the To maximize the benefits of encapsulation, the ADT top of the stack. Although the reserved word private (hen% class) is composed of two parts: the specifkxtion prevents client code from accessing the data representation and the implementation, The specification is the interface directly, encapsulation and are violated Permission to copy without fee all or part of this material is in ways this paper will discuss. Sections 2 and 3 describe granted provided that the copies are not made or distributed for the problem as it affects encapsulation and information direet commercial advantage, the ACM copyright notice and the title of the publication and Its date appear, and notice is given hiding, and Section 4 presents two programming techniques that COPYI is by permlseion of the Association of Computing for stnmgthening the separation of specitkation from Machinery.Y o COPYotherwise, or to republish, requkas a fee implementation opaque types and abstract classes. %#E??W?W#%lle TN”SA @ 1995 ACM O-89791-8!k3-x195/0003....50.5O

24 2. Encapdabn facilities to concentrate on the essentials. Infor- mation hiding makes this possible by separating In C++ classes, encapsulation is enforced, in one sense, by function tiom implementation, and should be strict access controls in the form of the private and viewed by client programmers as help rather than protect ed reserved words. Components of the class that hindrance. are private and protected are inaccessible to client code, Yet encapsulation is weakened by requiring that the At best, the visibility of an ADT’s data representation private part be located in the interface. If the implementor is a distraction whereby the user may spend time speculat- changes the ADT’s data representation, all client code must ing about further implementation details. At worst, the user be recompiled as well as relinked. This dependency is may misinterpret the data representation and make decisions inconsistent with the very notion of encapsulation. based on the misinterpretation, Here is a case in point. Stroustrup addresses this issue in Z%e C++ Program- In my CS2 course, the textbook llhmd94] proceeds ming Lunguage [Stro91]: slowly from abstraction to implementation. Students are introduced to the properties of common data struc- Why, you may ask was C++ designed in such a tures-lists, stacks, queues, sets-long before they learn way that recompilation of users of a class is how to implement them. For an assignment using queues necessary after a change to the private part? as abstractions, I gave the students the specification of a And why indeed need the private part be present queue class (as a . h file) and the compiled in the class declaration at all? In other words, form-but not the source code-of the implementai~on since users of a class are not allowed to access (. CPP) file. The assignment involved falling a queue and the private members, why must their declarations processing the characters in certain ways. This is the be present in the header files the user is sup- header file, abridged by deleting comments: posed to read? The answer is run-time ejicien- cy. On many systems, both the compilation // Header file cqueue .h process and the sequence of operations imple- #include “bool .h” menting a function call are simpler when the size of automatic objects (objects on the stack) is const int MAX_LENG = 101; known at compile time. class CharQueue { C++ is often used for applications nx@ring efficienc public: Boolean IsEmpty ) const; compact code. Particularly in system programming, size Boolean IsFull (; const; and speed of programs can be critical. For many applica- void Enqueue char ) ; tions, however, run-time efficiency is secondary to the char Front ( ) const; modukity and pliability afforded by strong encapsulation void Dequeue ); of implementation details, Section 4 of this paper describes CharQueue ( ) ; private: how to remove details of the data representation tlom the int front; class declaration. int rear; char data [MA2_LENG ] ; 3. Information Hiding );

Related to encapsulation is the concept of information The constant MALLENG is purely au implementation detail. hiding-the suppression of implementation details that do It is needed only to tell the compiler the size of the array not affect the use of an abstraction. Even though the representing the queue. private members of a C++ class are inaccessible to client Most students used the I SFU 11 ( ) operation to code, they are visibly exposed to the user as a human. Here determine when the queue was full. But some students is Bertrand Meyer’s perspective on information hiding bypassed this operation, assuming (incorrectly) that the meye88]: maximum queue length was 101. These students ran into trouble because the implementation algorithms use a circular It is worth recalling ... that the purpose of infor- buffer in which one array element must always be left mation hiding is abstraction, not protection. We empty. The maximum queue length is therefore 100, not do not necessarily wish to prevent client pro- 101. Had the private data representation and MAX.LENG grammers from accessing secret class elements, been absent from the header file, the students would h:we but rather to relieve them from having to do so. used ISFU11 ( ) as intended. In a software project, programmers are faced with too much information, and need abstraction

25 4. Decoupling Teehniquee struct PrivateRec { int front; Given the desirability of completely decoupling an ADT’s int rear; char data [MAX_LENG ] ; specification from its implementation, there are two tech- }; niques for removing details of the concrete data representa- tion flom a C++ class declaration, In this discussion it is CharQueue :: CharQueue ( ) / / Constructor assumed that the class declaration appears in a specifkation { (. h) fde and the class implementation appears in an p = new PrivateRec; p-> front = O; implementation (. cpp) file, p->rear = O; } 4.1 Opaque Types CharQueue: : -CharQueue ( ) // Destructor An opaque type is a record (st ruct) type whose forward { delete p; declaration appears in the specification fde and whose } complete declaration is hidden away in the implementation ffle. ‘Ile private data in the class declaration consists of void CharQueue: : Enqueue ( char newItem ) one item only: a pointer to an object of this opaque type { D4ead91], Consider the following specification of the p->rear . (p->rear + 1) % MAX_LENG; data [p->rear] = newItem; CharQueue ckiss: 1 // Header file cqueue .h If the client declares a CharQueue object named #include “bool. h“ myQueue, the resulting abstract memory diagram is as follows: st ruct Privat eRec; / / Forward dec larat ion // (Complete declara- // tion is in imple- FfIFmstom myQueue .p // mentation file) unnemeddynami erecold class CharQueue { public: 4 ““”’ El Boolean IsRnpty ( ) const; 1“’ n .: &ta [01 [11 [21 . . . [1OOI void Dequeue ( ) ; CharQueue ( ) ; // Constructor -CharQueue ( ) ; // Destructor private: PrivateRec* p; // Pointer to a The implementor is now free to change the implementation }; // PrivateRec (including the data representation) in a manner completely transparent to the client. Since the data representation is The full declaration of PrivateRec will appear in the encapsulated within the implementation file rather than the implementation file and will describe the concrete data specification file, the client need only be relinked, not representation of a queue. The only private member of the recompiled. For example, the data representation for class is thus a simple pointer, p. CharQueue might be changed to a dynamic, singly-linked Below is a portion of the implementation fde. When list by changing the implementation file as follows: a CharQueue object is created, the class constructor allocates a Privat eRec object on the free stem (heap) // Implemental ion file cqueue. cpp and initializes the front and rear members to zero. when a CharQueue object is destroyed, the class destruc- #include “ cqueue .h” ~inelud~ z=tddef .h> tor deallocates rhe piVWe record. / / For NULL <. struct NodeType { // Implemental ion file cqueue. cpp ink value; NodeType* 1 ink; #include “ cqueue. h” }; const int MAX_LENG = 101; struct PrivateRec { NodeType* fron<; NodeType* rear; };

26 CharQueue ::CharQueue () // Constructor abstract class is a class with one or more pure vinual { $mctions-membef functions whose prototypes are prefwed p = new PrivateRec; with the word vi rt ua 1 and are suffixed with the symbols p->front . NULL; p->rear . NULL; “. O”. Here is the declaration of CharQueue, revwitterl as } an abstract class:

CharQueue: :-CharQueue( ) // Destructor // Header file cqueue .h { . : // Code to deallocate #include “bool .h” // all list nodes class CharQueue { delete p; public: } virtual Boolean IsEmpty ( ) const = 0; virtual Boolean IsFull ( ) const = O; With this implementation, theabstractmemory diagramk virtual void Enqueue ( char ) = 0; the following: virtual char Front ( ) const = O; virtual void Dequeue ( ) = O; };

Because there is no private pat in this class declara- tion, no implementation details are exposed to the user (the ‘% student). But there is a catch: the compiler prevents you from creating objects of an abstract class. Using fitfi- tance, you must derive a new class from the abstract class, adding private data and overriding the virtual functions. Then you can create objects of the derived class. For L example, to use an array representation of a queue, you could derive a class CharQueueVec from CharQueue as Theadvantagesofopaquetypesare twofold. FirsL the follows. (This header fde would not be presented to tie techniquepmvides atotaldecoupling ofthespeeifkation of students.) an ADT from its implementation (not only the operations but also the data representation). This yields a purer // Header file cqueue2 .h abstraction, a stronger &gree of encapsulation, and en- haneedmodukwity, Changes tothedata representation are #include “ cqueue. h“ loedized in the implementation file and do not affect client const int MAX_LENG = 101; code (other than the need for relining). Second, opaque types reinforce information hiding. The user of a class can class CharQueueVec : public CharQueue ( no longer be influenced by partial knowledge or misinter- public: pretation of implementation details. Boolean IsEmpty ( ) const; Boolean IsFull ( ) const; The disadvantage of opaque types is the loss of void Enqueue ( char ) ; efficiency resulting from allocating and &allocating the char Front ( ) const; private reeord on the free store as well as the extra time void Dequeue ( ) ; required for indirect addressing through pointers. This CharQueueVec ( ) ; concern, along with the issue of compatibility with C private: int front; language programs, led to rejection of the idea of making int rear; opaque types intrinsic to all C++ classes [Stro91]. A char data [MAX_LENG] ; central theme in the design of the C++ language was that }; programs should not have to suffer the overhead associated with features they don’t use. Since C and C++ are often The corresponding implementation fde cqueue2 . cpp used when efficiency is paramount it is appropriate that the would look like that of the array implementation discussed use of opaque types should be optionata programming earlier, apart from substitution of the identifier technique rather than a mandatory language mechanism. CharQueueVec for each occurrence of the Ment.ifier CharQueue. 4.2 Abstract Classes Although students cannot declare objects of type CharQueue, they can declare variables of type A second techuique for decoupling specification fmm CharQueue * (pointer to CharQueue) or CharQueue& implementation is to use an absfract class. In C++, an (reference to CharQueue). Effectively, the student must

‘)7 L, bundle an entire program into a function receiving a are not inherited by a derived class. The student then reference to a CharQueue: must accept on faith that the derived class written by the instructor does include a constructor. void StudentFunc( CharQueue& my Queue ) { Given these disadvantages, I fmd opaque types preferable to : . // Manipulate myQueue abstract classes. For additional discussion of abstract } classes, see [stro91].

The instructor would wrap the student’s function into an 5. Conclusion “envelope”-a program that creates a CharQueueVec object and passes it to the student’s function: The C++ class mechanism provides only partial separation of specification from implementation, For efficient transla- #include “queue2. h“ tion and execution, the compiler requires the declarations of void StudentFunc( CharQueue& ) ; private data to appear in the class declaration. When giving prewritten classes to students, a purer int maino abstraction is obtained by moving an ADT’s data represen- { tation fmm the specifkation (. h) fde into the implementa- CharQueueVec q; tion file. This hides the data representation from the eyes StudentFunc (q) ; of the user and allows changes to the data representation return O; without recompiling client code. } Opaque types and abstract classes are two techniques for completely removing implementation details from the Because the member functions of the base class specification file. Opaque types are easier to use and CharQueue are declared as virtual, run-timebinding explain, but they are less ei%cient in their implementation. ensmes that within StudentFunc, the queue operations However, this loss of efilciency is likely to be inconsequen- execute darethos edefinedbythe CharQueueVec class. tial in the early coursework of the Abstract classes have two kenefits. First, as with Cumiculum. opaque types, the private data representation of an ADT is hidden from the user, and changes to the data representation References do not require recompiling the client code. Second, abstract classesyield better run-time performance than opaque types; ~ead91] M. Headington, “Opaque types in C++,” dynamic data and pointers are not required in the implemen- SCOOP-West 1991 Conference Proceedings, tations of the member functions. March 1991, pp. 17-23. However, the drawbacks to W technique in a CS1/CS2 setting are numerous ~ead94] M. Headington and . Riley, Data Abstraction and Structures Using C++, D.C. Heath and ● If the header file cqueue2. h k withheld from the Company, 1994. studen~ the student cannot write a main function to test his or her code. Only the StudentFunc function can Neye88] B. Meyer, Object-Oriented Software Construc- be written, to be sulmitted to the instructor for running. twn, Prentice Hall, 1988. ● If the header file cqueue2. h is given to the student to allow a main function to be written, then information Mica88] J. Micallef, “Encapsulation, reusability and hiding is lost. The private data representation now is extensibility in object-oriented programming visible-a problem we were trying to avoid in the tirst languages,” Journal of Object-Oriented Pro- place. graming 1 (l), April/May 1988, pp. 12-38. ● Given the header ffle cqueue. h, the student will need some explanation of virtual funcdons and the “pure- [Snyd861 A. Snyder, “Encapsulation and inheritance in specitler” (= O). This may require considerable hand- object-oriented programming,” 00PSLA ’86 waving, especially if inheritance and run-time binding Conference Proceedings, September 1986, pp. have not been discussed yet. (With opaque types, some 38-45. hand-waving is also necessary to explain the pointer in the private p- but the explanation is briefer,) [stro91] B. Stroustrup, Z%? C++ Programming Lun- ● The student may ask why the abstract class guage, 2nd Edition, Addison-Wesley, 1991. CharQueue has no constructor. The answer requires discussion of the fact that constructor and destructors

28