I l@ve RuBoard Previous Section Next Section

4.1 How to Implement a Class

OK, where do we start? In general, we start with an abstraction. Consider a stack. A stack is a fundamental abstraction of computer science. It allows for the nesting and retrieval of values in a last-in, first-out sequence. We nest values by pushing a new value onto the stack, and we retrieve them by popping the last value pushed on the stack. Other operations that users often require are to ask whether a stack is full or empty and to determine the size of the stack. A stack may also support peeking at the last value pushed on the stack.

In the description of a stack I italicized the words that represent operations that users will likely want to apply to objects of our stack class.

What type of elements should we store? A general stack should store all types. We do this by defining the stack as a class template. Because class templates are the topic of Chapter 6 and we're only in Chapter 4, we'll define a nontemplate stack class to hold string class objects.

A class declaration begins with the keyword class followed by a user-specified class name:



class Stack; 

This statement serves as a forward declaration of the Stack class; it introduces the class name to the compiler but provides no details of the operations it supports or the data members it contains. A forward declaration allows us to define class pointers and to use the class as a type specifier for class declarations:



// ok: these uses require a forward declaration of the class 


Stack *pt = 0; 


void process( const Stack& ); 

The class definition is necessary before we can define an actual Stack class object or refer to any members of the class. The skeleton of a class definition looks like this:



class Stack { 


public: 


   // ... public interface 


private: 


   // ... private implementation 


}; 

The definition of a class consists of the class declaration followed by the class body enclosed in curly braces and terminated by a semicolon. The public and private keywords within the class body control access to the members declared within each section. Public members can be accessed from anywhere within the program. Private members can be accessed only by the member functions and friends of the class ?later I explain what a friend is (or at least what a friend within the C++ language is). Here is the beginning of our Stack class definition:



class Stack { 


public: 


   // each operation returns true if able to be carried out 


   // pop and peek place the string value within elem 


   bool   push( const string& ); 


   bool   pop(  string &elem ); 


   bool   peek( string &elem ); 





   bool   empty(); 


   bool   full(); 





   // definition of size() is placed within class 


   // other members are simply declared ... 


   int    size() { return _stack.size(); } 


private: 


   vector<string> _stack; 


}; 

Our Stack class definition supports the six operations we identified at the start of this section. The elements themselves are stored in a vector of strings we've named _stack. (My coding convention is to prepend data members with an underscore.) Here is how we might define and use a Stack class object:



void fill_stack( Stack &stack, istream &is = cin ) 


{ 


    string str; 


    while ( is >> str && ! stack.full() ) 


            stack.push( str ); 





    cout << "Read in " << stack.size() << " elements\n"; 


} 

All member functions must be declared within the class definition. Optionally, a member function can also be defined inside the class definition. If defined within the body of the class, the member function is automatically treated as being inline. size(), for example, is an inline member of Stack.

To define a member function outside the class definition, we use a special declaration syntax. Its purpose is to identify the function as a member of a particular class. If the function is intended to be inline, the inline keyword must be specified:



inline bool 


Stack::empty() 


{ 


    return _stack.empty(); 


} 





bool 


Stack::pop( string &elem ) 


{ 


    if ( empty() ) 


         return false; 





    elem = _stack.back(); 


    _stack.pop_back(); 


    return true; 


} 

The syntax



Stack::empty() 

tells the compiler (and reader) that we are referring to the member empty() of the Stack class ?as opposed, say, to that of the vector or the string class. The name of the class followed by the double colon (Stack::) is called the class scope operator.

There is no difference in the treatment of an inline function if it is defined within or outside the class definition. As with the definition of a nonmember inline function, an inline member function should be placed in a header file. The class definition and the inline member functions are typically placed in a header file given the name of the class. For example, the Stack class definition and the definition of empty() would be placed inside a header file named Stack.h. This is what the user includes whenever he wishes to use our class.

The non-inline member functions are defined within a program text file, usually given the name of the class followed by one of the following suffixes: .C, .cc, .cpp, and .cxx (the x represents the reclining +). Microsoft Visual C++, for example, uses .cpp by default. The convention at Disney Feature Animation is to use .C. The convention at Dreamworks Animation is to use .cc.

Here are the remaining Stack member function definitions. full() compares the current size of the underlying vector with max_size(), the largest possible size of the vector. push() inserts an element provided that the _stack is not full.



inline bool Stack::full() 


     { return _stack.size() == _stack.max_size(); } 





bool Stack::peek( string &elem ) 


{ 


    if ( empty() ) 


         return false; 





    elem = _stack.back(); 


    return true; 


} 





bool Stack::push( const string &elem ) 


{ 


    if ( full() ) 


         return false; 





    _stack.push_back( elem ); 


    return true; 


} 

Although we've provided definitions for the full set of user operations, this is not yet a complete Stack class definition. In the next section, we walk through how to provide special initialization and deinitialization functions called the class constructor and destructor.

Exercise 4.1

Create a Stack.h and a Stack.suffix, where suffix is whatever convention your compiler or project follows. Write a main() function to exercise the full public interface, and compile and execute it. Both the program text file and main() must include Stack.h:



#include "Stack.h" 

Exercise 4.2

Extend the Stack class to support both a find() and a count() operation. find() returns true or false depending on whether the value is found. count() returns the number of occurrences of the string. Reimplement the main() of Exercise 4.1 to invoke both functions.

    I l@ve RuBoard Previous Section Next Section