Scoped Enumerations (C++11)

Traditional enumerations have some problems. One is that enumerators from two different enum definitions can conflict. Suppose you were working on a project involving eggs and T-shirts. You might try something like this:

enum egg {Small, Medium, Large, Jumbo};
enum t_shirt {Small, Medium, Large, Xlarge};

This won’t fly because the egg Small and the t_shirt Small would both be in the same scope, and the names conflict. C++11 provides a new form of enumeration that avoids this problem by having class scope for its enumerators. The declarations for this form look like this:

enum class egg {Small, Medium, Large, Jumbo};
enum class t_shirt {Small, Medium, Large, Xlarge};

Alternatively, you can use the keyword struct instead of class. In either case, you now need to use the enum name to qualify the enumerator:

egg choice = egg::Large;        // the Large enumerator of the egg enum
t_shirt Floyd = t_shirt::Large; // the Large enumerator of the t_shirt enum

Now that the enumerators have class scope, enumerators from different enum definitions no longer have potential name conflicts, and your egg-and-T-shirt project can proceed.

C++11 also tightens up type security for scoped enumerations. Regular enumerations get converted to integer types automatically in some situations, such as assignment to an int variable or being used in a comparison expression, but scoped enumerations have no implicit conversions to integer types:

enum egg_old {Small, Medium, Large, Jumbo};        // unscoped
enum class t_shirt {Small, Medium, Large, Xlarge}; // scoped
egg_old one = Medium;                              // unscoped
t_shirt rolf = t_shirt::Large;                     // scoped
int king = one;      // implicit type conversion for unscoped
int ring = rolf;     // not allowed, no implicit type conversion
if (king < Jumbo)    // allowed
    std::cout << "Jumbo converted to int before comparison. ";
if (king < t_shirt::Medium)   // not allowed
    std::cout << "Not allowed: < not defined for scoped enum. ";

But you can do an explicit type conversion if you feel you have to:

int Frodo = int(t_shirt::Small); // Frodo set to 0

Enumerations are represented by some underlying integer type, and under C98 that choice was implementation-dependent. Thus, a structure containing an enumeration might be of different sizes on different systems. C++11 removes that dependency for scoped enumerations. By default, the underlying type for C++11 scoped enumerations is int. Furthermore, there’s a syntax for indicating a different choice:

// underlying type for pizza is short
enum class : short pizza {Small, Medium, Large, XLarge};

The : short specifies the underlying type to be short. The underlying type has to be an integer type. Under C++11, you also can use this syntax to indicate the underlying type for an unscoped enumeration, but if you don’t choose the type, the choice the compiler makes is implementation-dependent.

Abstract Data Types

The Stock class is pretty specific. Often, however, programmers define classes to represent more general concepts. For example, using classes is a good way to implement what computer scientists describe as abstract data types (ADTs). As the name suggests, an ADT describes a data type in a general fashion without bringing in language or implementation details. Consider, for example, the stack. By using the stack, you can store data so that data is always added to or deleted from the top of the stack. For example, C++ programs use a stack to manage automatic variables. As new automatic variables are generated, they are added to the top of the stack. When they expire, they are removed from the stack.

Let’s look at the properties of a stack in a general, abstract way. First, a stack holds several items. (That property makes it a container, an even more general abstraction.) Next, a stack is characterized by the operations you can perform on it:

• You can create an empty stack.

• You can add an item to the top of a stack (that is, you can push an item).

• You can remove an item from the top (that is, you can pop an item).

• You can check whether the stack is full.

• You can check whether the stack is empty.

You can match this description with a class declaration in which the public member functions provide an interface that represents the stack operations. The private data members take care of storing the stack data. The class concept is a nice match to the ADT approach.

The private section has to commit itself to how to hold the data. For example, you can use an ordinary array, a dynamically allocated array, or some more advanced data structure, such as a linked list. However, the public interface should hide the exact representation. Instead, it should be expressed in general terms, such as creating a stack, pushing an item, and so on. Listing 10.10 shows one approach. It assumes that the bool type has been implemented. If it hasn’t been implemented on your system, you can use int, 0, and 1 rather than bool, false, and true.

Listing 10.10. stack.h


// stack.h -- class definition for the stack ADT
#ifndef STACK_H_
#define STACK_H_

typedef unsigned long Item;

class Stack
{
private:
    enum {MAX = 10};    // constant specific to class
    Item items[MAX];    // holds stack items
    int top;            // index for top stack item
public:
    Stack();
    bool isempty() const;
    bool isfull() const;
    // push() returns false if stack already is full, true otherwise
    bool push(const Item & item);   // add item to stack
    // pop() returns false if stack already is empty, true otherwise
    bool pop(Item & item);          // pop top into item
};
#endif


In the example in Listing 10.10, the private section shows that the stack is implemented by using an array, but the public section doesn’t reveal that fact. Thus, you can replace the array with, say, a dynamic array without changing the class interface. This means changing the stack implementation doesn’t require that you recode programs that use the stack. You just recompile the stack code and link it with existing program code.

The interface is redundant in that pop() and push() return information about the stack status (full or empty) instead of being type void. This provides the programmer with a couple options as to how to handle exceeding the stack limit or emptying the stack. He or she can use isempty() and isfull() to check before attempting to modify the stack, or else use the return value of push() and pop() to determine whether the operation is successful.

Rather than define the stack in terms of some particular type, the class describes it in terms of a general Item type. In this case, the header file uses typedef to make Item the same as unsigned long. If you want, say, a stack of doubles or of a structure type, you can change the typedef and leave the class declaration and method definitions unaltered. Class templates (see Chapter 14, “Reusing Code in C++”) provide a more powerful method for isolating from the class design the type of data stored.

Next, you need to implement the class methods. Listing 10.11 shows one possibility.

Listing 10.11. stack.cpp


// stack.cpp -- Stack member functions
#include "stack.h"
Stack::Stack()    // create an empty stack
{
    top = 0;
}

bool Stack::isempty() const
{
    return top == 0;
}

bool Stack::isfull() const
{
    return top == MAX;
}

bool Stack::push(const Item & item)
{
    if (top < MAX)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

bool Stack::pop(Item & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}


The default constructor guarantees that all stacks are created empty. The code for pop() and push() guarantees that the top of the stack is managed properly. Guarantees like this are one of the things that make OOP reliable. Suppose that, instead, you create a separate array to represent the stack and an independent variable to represent the index of the top. In that case, it is your responsibility to get the code right each time you create a new stack. Without the protection that private data offers, there’s always the possibility of making some program blunder that alters data unintentionally.

Let’s test this stack. Listing 10.12 models the life of a clerk who processes purchase orders from the top of his in-basket, using the LIFO (last-in, first-out) approach of a stack.

Listing 10.12. stacker.cpp


// stacker.cpp -- testing the Stack class
#include <iostream>
#include <cctype>  // or ctype.h
#include "stack.h"
int main()
{
    using namespace std;
    Stack st; // create an empty stack
    char ch;
    unsigned long po;
    cout << "Please enter A to add a purchase order, "
        << "P to process a PO, or Q to quit. ";
    while (cin >> ch && toupper(ch) != 'Q')
    {
        while (cin.get() != ' ')
            continue;
        if (!isalpha(ch))
        {
            cout << 'a';
            continue;
        }
        switch(ch)
        {
             case 'A':
             case 'a': cout << "Enter a PO number to add: ";
                       cin >> po;
                       if (st.isfull())
                           cout << "stack already full ";
                       else
                           st.push(po);
                       break;
             case 'P':
             case 'p': if (st.isempty())
                           cout << "stack already empty ";
                       else {
                           st.pop(po);
                           cout << "PO #" << po << " popped ";
                       }
                       break;
        }
        cout << "Please enter A to add a purchase order, "
             << "P to process a PO, or Q to quit. ";
    }
    cout << "Bye ";
    return 0;
}


The little while loop in Listing 10.12 that gets rid of the rest of the line isn’t absolutely necessary at this point, but it will come in handy in a modification of this program in Chapter 14. Here’s a sample run:

Please enter A to add a purchase order,
P to process a PO, or Q to quit.
A
Enter a PO number to add: 17885
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
P
PO #17885 popped
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
A
Enter a PO number to add: 17965
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
A
Enter a PO number to add: 18002
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
P
PO #18002 popped
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
P
PO #17965 popped
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
P
stack already empty
Please enter A to add a purchase order,
P to process a PO, or Q to quit.
Q
Bye

Summary

OOP emphasizes how a program represents data. The first step toward solving a programming problem by using the OOP approach is to describe the data in terms of its interface with the program, specifying how the data is used. Next, you need to design a class that implements the interface. Typically, private data members store the information, whereas public member functions, also called methods, provide the only access to the data. The class combines data and methods into one unit, and the private aspect accomplishes data hiding.

Usually, you separate a class declaration into two parts, typically kept in separate files. The class declaration proper goes into a header file, with the methods represented by function prototypes. The source code that defines the member functions goes into a methods file. This approach separates the description of the interface from the details of the implementation. In principle, you need to know only the public class interface to use the class. Of course, you can look at the implementation (unless it’s been supplied to you in compiled form only), but your program shouldn’t rely on details of the implementation, such as knowing that a particular value is stored as an int. As long as a program and a class communicate only through methods defining the interface, you are free to improve either part separately without worrying about unforeseen interactions.

A class is a user-defined type, and an object is an instance of a class. This means an object is a variable of that type or the equivalent of a variable, such as memory allocated by new according to the class specification. C++ tries to make user-defined types as similar as possible to standard types, so you can declare objects, pointers to objects, and arrays of objects. You can pass objects as arguments, return them as function return values, and assign one object to another of the same type. If you provide a constructor method, you can initialize objects when they are created. If you provide a destructor method, the program executes that method when the object expires.

Each object holds its own copies of the data portion of a class declaration, but they share the class methods. If mr_object is the name of a particular object and try_me() is a member function, you invoke the member function by using the dot membership operator: mr_object.try_me(). OOP terminology describes this function call as sending a try_me message to the mr_object object. Any reference to class data members in the try_me() method then applies to the data members of the mr_object object. Similarly, the function call i_object.try_me() accesses the data members of the i_object object.

If you want a member function to act on more than one object, you can pass additional objects to the method as arguments. If a method needs to refer explicitly to the object that evoked it, it can use the this pointer. The this pointer is set to the address of the evoking object, so *this is an alias for the object itself.

Classes are well matched to describing ADTs. The public member function interface provides the services described by an ADT, and the class’s private section and the code for the class methods provide an implementation that is hidden from clients of the class.

Chapter Review

1. What is a class?

2. How does a class accomplish abstraction, encapsulation, and data hiding?

3. What is the relationship between an object and a class?

4. In what way, aside from being functions, are class function members different from class data members?

5. Define a class to represent a bank account. Data members should include the depositor’s name, the account number (use a string), and the balance. Member functions should allow the following:

• Creating an object and initializing it.

• Displaying the depositor’s name, account number, and balance

• Depositing an amount of money given by an argument

• Withdrawing an amount of money given by an argument

Just show the class declaration, not the method implementations. (Programming Exercise 1 provides you with an opportunity to write the implementation.)

6. When are class constructors called? When are class destructors called?

7. Provide code for a constructor for the bank account class from Chapter Review Question 5.

8. What is a default constructor? What is the advantage of having one?

9. Modify the Stock class definition (the version in stock20.h) so that it has member functions that return the values of the individual data members. Note: A member that returns the company name should not provide a weapon for altering the array. That is, it can’t simply return a string reference. It could return a const reference.

10. What are this and *this?

Programming Exercises

1. Provide method definitions for the class described in Chapter Review Question 5 and write a short program that illustrates all the features.

2. Here is a rather simple class definition:

class Person {
private:
    static const LIMIT = 25;
    string lname;       // Person's last name
    char fname[LIMIT];  // Person's first name
public:
    Person() {lname = ""; fname[0] = '';  } // #1
    Person(const string & ln, const char * fn = "Heyyou");   // #2
// the following methods display lname and fname
    void Show() const;        // firstname lastname format
    void FormalShow() const;  // lastname, firstname format
};

(It uses both a string object and a character array so that you can compare how the two forms are used.) Write a program that completes the implementation by providing code for the undefined methods. The program in which you use the class should also use the three possible constructor calls (no arguments, one argument, and two arguments) and the two display methods. Here’s an example that uses the constructors and methods:

Person one;                       // use default constructor
Person two("Smythecraft");        // use #2 with one default argument
Person three("Dimwiddy", "Sam");  // use #2, no defaults
one.Show();
cout << endl;
one.FormalShow();
// etc. for two and three

3. Do Programming Exercise 1 from Chapter 9 but replace the code shown there with an appropriate golf class declaration. Replace setgolf(golf &, const char*, int) with a constructor with the appropriate argument for providing initial values. Retain the interactive version of setgolf() but implement it by using the constructor. (For example, for the code for setgolf(), obtain the data, pass the data to the constructor to create a temporary object, and assign the temporary object to the invoking object, which is *this.)

4. Do Programming Exercise 4 from Chapter 9 but convert the Sales structure and its associated functions to a class and its methods. Replace the setSales(Sales &, double [], int) function with a constructor. Implement the interactive setSales(Sales &) method by using the constructor. Keep the class within the namespace SALES.

5. Consider the following structure declaration:

struct customer {
    char fullname[35];
    double payment;
};

Write a program that adds and removes customer structures from a stack, represented by a Stack class declaration. Each time a customer is removed, his or her payment should be added to a running total, and the running total should be reported. Note: You should be able to use the Stack class unaltered; just change the typedef declaration so that Item is type customer instead of unsigned long.

6. Here’s a class declaration:

class Move
{
private:
    double x;
    double y;
public:
    Move(double a = 0, double b = 0);   // sets x, y to a, b
    showmove() const;                   // shows current x, y values
    Move add(const Move & m) const;
// this function adds x of m to x of invoking object to get new x,
// adds y of m to y of invoking object to get new y, creates a new
// move object initialized to new x, y values and returns it
    reset(double a = 0, double b = 0);  // resets x,y to a, b
};

Create member function definitions and a program that exercises the class.

7. A Betelgeusean plorg has these properties:

Data

A plorg has a name with no more than 19 letters.

A plorg has a contentment index (CI), which is an integer.

Operations

A new plorg starts out with a name and a CI of 50.

A plorg’s CI can change.

A plorg can report its name and CI.

The default plorg has the name "Plorga".

Write a Plorg class declaration (including data members and member function prototypes) that represents a plorg. Write the function definitions for the member functions. Write a short program that demonstrates all the features of the Plorg class.

8. You can describe a simple list as follows:

• The simple list can hold zero or more items of some particular type.

• You can create an empty list.

• You can add items to the list.

• You can determine whether the list is empty.

• You can determine whether the list is full.

• You can visit each item in the list and perform some action on it.

As you can see, this list really is simple; it doesn’t allow insertion or deletion, for example.

Design a List class to represent this abstract type. You should provide a list.h header file with the class declaration and a list.cpp file with the class method implementations. You should also create a short program that utilizes your design.

The main reason for keeping the list specification simple is to simplify this programming exercise. You can implement the list as an array or, if you’re familiar with the data type, as a linked list. But the public interface should not depend on your choice. That is, the public interface should not have array indices, pointers to nodes, and so on. It should be expressed in the general concepts of creating a list, adding an item to the list, and so on. The usual way to handle visiting each item and performing an action is to use a function that takes a function pointer as an argument:

void visit(void (*pf)(Item &));

Here pf points to a function (not a member function) that takes a reference to Item argument, where Item is the type for items in the list. The visit() function applies this function to each item in the list. You can use the Stack class as a general guide.

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

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