This new implementation of the Brass and BrassPlus accounts can be used in the same manner as the old one because the class methods have the same names and interfaces as before. For example, to convert Listing 13.10 to use the new implementation, you just need to take these steps to convert usebrass2.cpp to a usebrass3.cpp file:

• Link usebrass2.cpp with acctabc.cpp instead of with brass.cpp.

• Include acctabc.h instead of brass.h.

• Replace

Brass * p_clients[CLIENTS];

with

AcctABC * p_clients[CLIENTS];

Listing 13.13 shows the resulting file, renamed usebrass3.cpp.

Listing 13.13. usebrass3.cpp

// usebrass3.cpp -- polymorphic example using an abstract base class

// compile with acctacb.cpp

#include

#include

#include "acctabc.h"

const int CLIENTS = 4;

int main()

{

   using std::cin;

   using std::cout;

   using std::endl;

   AcctABC * p_clients[CLIENTS];

   std::string temp;

   long tempnum;

   double tempbal;

   char kind;

   for (int i = 0; i < CLIENTS; i++)

   {

       cout << "Enter client's name: ";

       getline(cin,temp);

       cout << "Enter client's account number: ";

       cin >> tempnum;

       cout << "Enter opening balance: $";

       cin >> tempbal;

       cout << "Enter 1 for Brass Account or "

            << "2 for BrassPlus Account: ";

       while (cin >> kind && (kind != '1' && kind != '2'))

           cout <<"Enter either 1 or 2: ";

       if (kind == '1')

           p_clients[i] = new Brass(temp, tempnum, tempbal);

       else

       {

           double tmax, trate;

           cout << "Enter the overdraft limit: $";

           cin >> tmax;

           cout << "Enter the interest rate "

                << "as a decimal fraction: ";

           cin >> trate;

           p_clients[i] = new BrassPlus(temp, tempnum, tempbal,

                                        tmax, trate);

        }

        while (cin.get() != '\n')

            continue;

   }

   cout << endl;

   for (int i = 0; i < CLIENTS; i++)

   {

       p_clients[i]->ViewAcct();

       cout << endl;

   }

   for (int i = 0; i < CLIENTS; i++)

   {

       delete p_clients[i];  // free memory

   }

   cout << "Done.\n";

   return 0;

}

The program itself behaves the same as the non-abstract base class version, so given the same input as for Listing 13.10, the output would be the same.

ABC Philosophy

The ABC methodology is a much more systematic, disciplined way to approach inheritance than the more ad hoc, spur-of-the-moment approach used by the RatedPlayer example. Before designing an ABC, you first have to develop a model of what classes are needed to represent a programming problem and how they relate to one another. One school of thought holds that if you design an inheritance hierarchy of classes, the only concrete classes should be those that never serve as a base class. This approach tends to produce cleaner designs with fewer complications.

One way of thinking about ABCs is to consider them an enforcement of interface. An ABC demands that its pure virtual functions be overridden in any concrete derived classes—forcing the derived class to obey the rules of interface the ABC has set. This model is common in component-based programming paradigms, in which the use of ABCs allows the component designer to create an “interface contract” where all components derived from the ABC are guaranteed to uphold at least the common functionality specified by the ABC.

Inheritance and Dynamic Memory Allocation

How does inheritance interact with dynamic memory allocation (the use of new and delete)? For example, if a base class uses dynamic memory allocation and redefines assignment and a copy constructor, how does that affect the implementation of the derived class? The answer depends on the nature of the derived class. If the derived class does not itself use dynamic memory allocation, you needn’t take any special steps. If the derived class does also use dynamic memory allocation, then there are a couple new tricks to learn. Let’s look at these two cases.

Case 1: Derived Class Doesn’t Use new

Suppose you begin with the following base class that uses dynamic memory allocation:

//  Base Class Using DMA

class baseDMA

{

private:

    char * label;

    int rating;

public:

    baseDMA(const char * l = "null", int r = 0);

    baseDMA(const baseDMA & rs);

    virtual ~baseDMA();

    baseDMA & operator=(const baseDMA & rs);

...

};

The declaration contains the special methods that are required when constructors use new: a destructor, a copy constructor, and an overloaded assignment operator.

Now suppose you derive a lackDMA class from baseDMA and that lackDMA does not use new or have other unusual design features that require special treatment:

// derived class without DMA

class lacksDMA :public baseDMA

{

private:

    char color[40];

public:

...

};

Do you now have to define an explicit destructor, copy constructor, and assignment operator for the lackDMA class? The answer is no.

First, consider the need for a destructor. If you don’t define one, the compiler defines a default destructor that does nothing. Actually, the default destructor for a derived class always does something; it calls the base-class destructor after executing its own code. Because the lackDMA members, we assume, don’t require any special action, the default destructor is fine.

Перейти на страницу:

Все книги серии Developer's Library

Похожие книги