Chapter 14. More on Classes

This method is, to define as the number of a class the class of all classes similar to the given class.

Bertrand Russell, Principles of Mathematics, Part II, Chapter 11, Section iii, 1903

C++ has many bells and whistles that give you a lot of flexibility in designing your classes. For example, the friend keyword lets a class specify ordinary functions that are allowed to access its private data. This section also covers constant members as well as how to constrain specific data using the keyword static.

Friends

In Chapter 13, you defined a basic stack class. Suppose you want to write a function to see whether two stacks are equal. At first glance this is simple. The function looks like Example 14-1.

Example 14-1. stack_c/s_equal.cpp
/********************************************************
 * stack_equal -- Test to see if two stacks are equal   *
 *                                                      *
 * Parameters                                           *
 *      s1, s2 -- the two stacks                        *
 *                                                      *
 * Returns                                              *
 *      0 -- stacks are not equal                       *
 *      1 -- stacks are equal                           *
 ********************************************************/
int stack_equal(const stack& s1, const stack& s2)
{
    int index;  // Index into the items in the array

    // Check number of items first
    if (s1.count != s2.count)
        return (0);

    for (index = 0; index < s1.count; ++index) {

        assert((index >= 0) && 
               (index < sizeof(s1.data)/sizeof(s1.data[0])));

        assert((index >= 0) && 
               (index < sizeof(s2.data)/sizeof(s2.data[0])));

        if (s1.data[index] != s2.data[index])
            return (0);
    }
    return (1);
}

Like many programs, this solution is simple, clear, and wrong. The problem is that the member variables count and data are private. That means you can’t access them.

So what do you do? One solution is to make these variables public. That gives the function stack_equal access to count and data. The problem is that it also gives everyone else access, and you don’t want that.

Friend Functions

Fortunately C++ gives you a way to say, “Let stack_equal and only stack_equal have access to the private data of the class stack.” This is accomplished through the friend directive. Classes must declare their friends. No function from the outside may access the private data from the class, unless the class allows it.

Example 14-2. stack_c/f_stack.cpp
// The stack itself
class stack {
    private:
        int count;              // Number of items in the stack
        int data[STACK_SIZE];   // The items themselves
    public:
        // Initialize the stack
        void init(  );

        // Push an item on the stack
        void push(const int item);

        // Pop an item from the stack
        int pop(  );

        friend int stack_equal(const stack& s1, const stack& s2);
};

stack_equal is not a member function of the class stack. It is a normal, simple function. The only difference is that because the function is a friend, it has access to private data for any class that calls it a friend.

Friend Classes

Friends are not restricted to just functions. One class can be a friend of another. For example:

class item {
    private:
        int data;

    friend class set_of_items;
};
class set_of_items {
    // ...
};

In this case, since the class set_of_items is a friend of item, it has access to all the members of item.

Constant Functions

C++ lets you define two types of numbers: constant and nonconstant. For example:

int index;                // Current index into the data array
const int DATA_MAX(100);  // Maximum number of items in the array

These two items are treated differently. For example, you can change the value of index, but you can’t change DATA_MAX.

Now let’s consider a class to implement a set of numbers from 0 to 31. The definition of this class is:

// Warning: The member functions in this class are incomplete
//          See below for a better definition of this class
class int_set {
    private:
        // ... whatever
    public:
        int_set(  );                       // Default constructor
        int_set(const int_set& old_set); // Copy constructor
        void set(int value);             // Set a value
        void clear(int value);           // Clear an element
        int test(int value);             // See whether an element is set
};

As with numbers, C++ will let you define two types of int_set objects: constant and nonconstant:

int_set var_set;      // A variable set (we can change this)

var_set.set(1);       // Set an element in the set

// Define a constant version of the set (we cannot change this)
const int_set const_set(var_set);

In the int_set class, there are member functions such as set and clear that change the value of the set. There is also a function test that changes nothing.

Obviously you don’t want to allow set and clear to be used on a constant. However, it is okay to use the test member function.

But how does C++ know what can be used on a constant and what can’t? The trick is to put the keyword const at the end of the function header. This tells C++ that this member function can be used for a constant variable. So if you put const after the member function test, C++ will allow it to be used in a constant. The member functions set and clear do not have this keyword, so they can’t be used in a constant.

class int_set {
    private:
        // ... whatever
    public:
        int_set(  );             // Default constructor
        int_set(const int_set& old_set); // Copy constructor
        void set(int value);   // Set a value
        void clear(int value); // Clear an element
        int test(int value) const;   // See whether an element is set
};

Thus, in your code you can do the following:

int_set var_set;      // A variable set (we can change this)

var_set.set(1);       // Set an element in the set (legal)

// Define a constant version of the set (we cannot change this)
const int_set const_set(var_set);

// In the next statement we use the member function "test" legally
std::cout << "Testing element 1. Value=" << const_set.test(  ) << '
';

However, you cannot do the following:

const_set.set(5);    // Illegal (set is not allowed on a const)

The member function set was not declared const, so it cannot be invoked on a const int_set object.

Constant Members

Classes may contain constant members. The problem is that constants behave a little differently inside classes than outside. Outside, a constant variable declaration must be initialized. For example:

const int data_size = 1024;	 // Number of data items in the input stream

Inside a class, constants are not initialized when they are declared. For example:

class data_list {
    public:
        const int data_size;   // Number of items in the list
    // ... rest of the class
};

Constant member variables are initialized by the constructor. However, it’s not as simple as this:

class data_list {
    public:
        const int data_size;   // Number of items in the list

        data_list(  ) {
            data_size = 1024;   // This code won't work
        };
    // ... rest of the class
};

Instead, because data_size is a constant, it must be initialized with a special syntax:

        data_list(  ) : data_size(1024) {
        };

But what happens if you want just a simple constant inside your class? Unfortunately C++ doesn’t allow you to do the following:

class foo {
    public: 
        const int foo_size = 100;  // Illegal

You are left with two choices:

  • Put the constant outside the code:

    const int foo_size = 100;	    // Number of data items in the list
    
    class foo {

    This makes foo_size available to all the world.

  • Use a syntax trick to fool C++ into defining a constant:

    class foo {
        public:
            enum {foo_size = 100};  // Number of data items in the list

    This defines foo_size as a constant whose value is 100. It does this by actually declaring foo_size as a element of an enum type and giving it the explicit value 100. Because C++ treats enums as integers, this works for defining integer constants.

    The drawbacks to this method are that it’s tricky, it works only for integers, and it exploits some holes in the C++ syntax that may go away as the language is better defined. Such code can easily cause difficulties for other programmers trying to maintain your code who aren’t familiar with the trick.

Static Member Variables

Suppose you want to keep a running count of the number of stacks in use at any given time. One way to do this is to create a global variable stack_count that is incremented in the stack constructor and decremented in the destructor:

int stack_count = 0; // Number of stacks currently in use

class stack {
    private:
        int count;         // Number of items in the stack
        // ... member variables
    public:
        int data_count;  // Number of items in the stack
        // ... member variables
        stack(  ) {
            // We just created a stack
            ++stack_count;
            count = 0;
        }
        ~stack(  ) {
            // We now have one less stack
            --stack_count;
        }
        // ... other member functions
};

Note that stack_count is a single global variable. No matter how many different stacks you create, there is one and only one stack_count.

Although this system works, it has some drawbacks. The definition of the class stack contains everything about the stack, except the variable stack_count. It would be nice to put stack_count in the class, but if you define it as a member variable, you’ll get a new copy of stack_count each time you declare a stack class variable.

C++ has a special modifier for member variables: static. This tells C++ that one and only one variable is to be defined for the class:

class stack {
    private:
        static int stack_count; // Number of stacks currently in use
        int count;         // Number of items in the stack
        // ... member variables
    public:
        stack(  ) {
            // We just created a stack
            ++stack_count;
            count = 0;
        }
        ~stack(  ) {
            // We now have one less stack
            --stack_count;
        }
        // ... other member functions
};

This new version looks almost the same as the global variable version. There is, however, one thing missing: the initialization of stack_count. This is done with the statement:

int stack::stack_count = 0; // No stacks have been defined

The difference between static and nonstatic member variables is that member variables belong to the object, while static member variables belong to the class. Thus if you create three stacks, you get three copies of the ordinary member variable data_count. However, since there is only one definition of the class, only one stack_count is created.

So if you have:

stack a_stack;
stack b_stack;

a_stack.stack_count is the same as b_stack.stack_count. There is only one stack_count for the class stack. C++ allows you to access static member variables using the syntax:

<class>::<variable>

Thus you can get to stack_count with the statement:

std::cout << "The number of active stacks is " << 
              stack::stack_count << '
';

(Or at least you could if stack_count was not private.)

You can use the dot notation (a_stack.stack_count), but this is considered poor programming style. That’s because it implies that stack_count belongs to the object a_stack. But because it’s static, the variable belongs to the class stack.

Static Member Functions

The member variable stack_count is defined as private. This means that nothing outside the class can access it. You want to know how many stacks are defined, so you need a function to get the value of stack_count. A first cut might be:

class stack {
        static int stack_count; // Number of stacks currently in use
        // ... member variables
    public:
        // Not quite right
        int get_count(  ) {
            return (stack_count);
        }        
        // ... other member functions
};

This works, but you need a stack type variable to access this function.

{
    stack temp_stack;    // Stack for getting the count
    std::cout << "Current count " << temp_stack.get_count(  ) << '
';
}

Because get_count doesn’t use any nonstatic data from stack, it can be made a static member function:

class stack {
        static int stack_count; // Number of stacks currently in use
        // ... member variables
    public:
        // Right
        static int get_count(  ) {
            return (stack_count);
        }        
        // ... other member functions
};

You can now access the static member function get_count much like you access the static member variable stack_count:

std::cout << "The number of active stacks is " << 
              stack::get_count(  ) << '
';

Static member functions are very limited. They can’t access nonstatic member variables or functions in the class. They can access static member data, static member functions, and functions and data outside the class.

The Meaning of static

The keyword static has many different meanings in C++. Table 14-1 is a complete list of the various ways static can be used.

Table 14-1. The meanings of static

Usage

Meaning

Variable outside the body of any function

The scope of the variable is limited to the file in which it is declared.

Variable declaration inside a function

The variable is permanent. It is initialized once, and only one copy is created even if the function is called recursively.

Function declaration

The scope of the function is limited to the file in which it is declared.

Member variable

One copy of the variable is created per class (not one per object).

Member function

Function can only access static members of the class.

Programming Exercises

Exercies 14-1: Two classes share a file. Other areas of the program need to know when this file is busy. Create a function that returns 1 when the file is being used by either of these two classes.

Exercies 14-2: You are asked to write a booking program for the veterinarian; Dr. Able Smith, PHD (Pigs, Horses, Dogs). Define a class type for each animal. Each class should keep track in a private static variable of the number of animals that have been defined using that class. Define a function that returns the total number of animals (all three types combined).

Exercies 14-3: Write a class in which each instance of the class can access a stack—not one stack per instance, but one stack, period. Any instance of the class can lock the stack for its own exclusive use and unlock it later. Define member functions to perform the lock and unlock functions.

As an added attraction, make the unlock function check to see that the current instance of the class was the same instance that locked the stack in the first place.

Exercies 14-4: You need to supply some I/O routines for handling lines in a file. The basic definition of the line-number class is:

class line_number {
    public:
        void goto_line(int line);
        int get_current_line(  );
        long int get_char_pos(  );
}

The member functions are defined as:

void goto_line(int line);

Positions the input file at specified line.

int get_current_line( );

Returns the current line number (as set by goto_line).

long int get_char_pos( );

Returns the character position of the current line. (This is the tricky one.)

Several line_number classes may be in use at any time. The class maintains its own internal list so that it knows which line_number classes are in use. When goto_line is called, the function will scan the list of line_number classes to find the one nearest the given line number and use it to start scanning for the given line number.

For example, suppose there are four active line_number variables:

Variable

Position

beginning

Line 0

chapter_start

Line 87

current_heading

Line 112

current_location

Line 52

You wish to move current_location to line 90. The goto_line function would search the list for the line nearest the new location (in this case chapter_start) and use it to jump to line 87. It then would read the file, character by character, until it saw three end-of-line characters to position itself at line 90.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.220.92