Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ For Dummies (2004) [eng].pdf
Скачиваний:
84
Добавлен:
16.08.2013
Размер:
8.09 Mб
Скачать

284 Part IV: Inheritance

 

 

 

 

Account

 

 

Figure 22-4:

 

Conventional

 

Timed

 

Market

 

 

 

 

 

 

A more

 

 

 

 

 

 

developed

Savings

Checking

CD

501K

Stock

MutualFunds

bank

 

 

 

 

 

 

account

 

SpecialChecking

 

 

 

 

hierarchy.

 

 

 

 

 

Suppose that the bank allows account holders to access checking and stock market accounts remotely. Withdrawals from other account types can be made only at the bank. Although the class structure in Figure 22-4 seems nat­ ural, the one shown in Figure 22-5 is also justifiable given this information. The programmer must decide which class structure best fits the data and leads to the cleanest, most natural implementation.

 

 

 

 

Account

 

 

 

 

RemotelyAccessible

 

LocallyAccessible

 

 

 

Figure 22-5:

 

 

 

 

 

 

An alternate

 

 

 

 

 

class

Stock

Checking

 

Market

Savings

hierarchy to

 

 

 

 

 

the one in

 

SpecialChecking

 

MutualFunds

 

Figure 22-4.

 

CD

501K

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Implementing Abstract Classes

As intellectually satisfying as factoring is, it introduces a problem of its own. Return one more time to the bank account classes, specifically the common base class Account. Think for a minute about how you might go about defin­ ing the different member functions defined in Account.

Most Account member functions are no problem because both account types implement them in the same way. Implementing those common functions with Account::withdrawal() is different, however. The rules for withdrawing from

Chapter 22: Factoring Classes 285

a savings account are different than those for withdrawing from a checking account. You’ll have to implement Savings::withdrawal() differently than you do Checking::withdrawal(). But how are you supposed to implement

Account::withdrawal()?

Let’s ask the bank manager for help. I imagine the conversation going some­ thing like the following:

“What are the rules for making a withdrawal from an account?” you ask expectantly.

“What type of account? Savings or checking?” comes the reply.

“From an account,” you say. “Just an account.”

Blank look. (One might say a “blank bank look” . . . then again, maybe not.)

The problem is that the question doesn’t make sense. There’s no such thing as “just an account.” All accounts (in this example) are either checking accounts or savings accounts. The concept of an account is an abstract one that factors out properties common to the two concrete classes. It is incomplete because it lacks the critical property withdrawal(). (After you get further into the details, you may find other properties that a simple account lacks.)

An abstract class is one that only exists in subclasses. A concrete class is a class that is not abstract. Hardly an abstract concept.

Let me borrow an example from the animal kingdom. You can observe the dif­ ferent species of warm-blooded, baby-bearing animals and conclude that there is a concept called mammal. You can derive classes from mammal, such as canine, feline, and hominid. It is impossible, however, to find anywhere on earth a pure mammal, that is, a mammal that isn’t a member of some sub­ species of mammal. Mammal is a high-level concept that man has created — no instances of mammal exist.

Note that I can make this assertion confidently although time has passed since I first wrote this (it took you only a few seconds to get from there to here — I hope). Scientists discover new animals all the time. One scientist even dis­ covered a new phylum in the 1990s (if you’re a biologist, that’s a big deal). Not once has a scientist come back and said, “This new thing is a mammal and nothing more . . . just a mammal.” The problem with a statement like this is that this animal surely has properties that other mammals don’t share and, even if doesn’t, there’s a distinct possibility that someone will find such a property in the future.

C++ supports a concept known as an abstract class to describe an incomplete concept such as mammal.

286 Part IV: Inheritance

Describing the abstract class concept

An abstract class is a class with one or more pure virtual functions. Oh, great! That helps a lot.

Okay, a pure virtual function is a virtual member function that is marked as having no implementation. Most likely it has no implementation because no implementation is possible with the information provided in the class, including any base classes.

It doesn’t make sense to ask exactly how to implement the withdrawal() function in the class Account. However, the concept of a withdrawal from an account does make sense. The C++ programmer can write a function withdrawal() that is impossible to implement. Such a function is called a pure virtual function. (Don’t ask me how they came up with that name.)

The syntax for declaring a function pure virtual is demonstrated in the follow­ ing class Account:

// Account - this class is an abstract class class Account

{

protected:

Account(Account& c); // avoid making any copies public:

Account(unsigned accNo, float initialBalance = 0.0F);

//access functions unsigned int accountNo( ); float acntBalance( ); static int noAccounts( );

//transaction functions void deposit(float amount);

//the following is a pure virtual function virtual void withdrawal(float amount) = 0;

protected:

//keep accounts in a linked list so there’s no limit

//to the number of accounts

static int count;

// number of accounts

unsigned

accountNumber;

float

balance;

 

};

The = 0 after the declaration of withdrawal() indicates that the programmer does not intend to define this function. The declaration is a placeholder for the subclasses. The subclasses of Account are expected to override this function

Chapter 22: Factoring Classes 287

with a concrete function. The programmer must provide an implementation for each member function not declared pure virtual.

I think this notation is silly, and I don’t like it any more than you do. But it’s here to stay, so you just have to learn to live with it. There is a reason, if not exactly a justification, for this notation. Every virtual function must have an entry in a special table. This entry contains the address of the function. The entry for a pure virtual function is zero. Some other languages define an abstract keyword — no, I mean a keyword abstract.

An abstract class cannot be instanced with an object; that is, you can’t make an object out of an abstract class. For example, the following declaration is not legal:

void fn( )

{

// declare an account with 100 dollars

Account acnt(1234, 100.00);// this is not legal acnt.withdrawal(50); // what would you expect

// this call to do?

}

If the declaration were allowed, the resulting object would be incomplete, lacking in some capability. For example, what should the preceding call do? Remember, there is no Account::withdrawal().

Abstract classes serve as base classes for other classes. An Account con­ tains all the properties associated with a generic bank account. You can create other types of bank accounts by inheriting from Account, but they can’t be instanced with an object.

Making an honest class out of an abstract class

The subclass of an abstract class remains abstract until all pure virtual func­ tions have been overridden. The class Savings is not abstract because it overrides the pure virtual function withdrawal() with a perfectly good defi­ nition. An object of class Savings knows how to perform withdrawal() when called on to do so. The same is true of class Checking. The class is not virtual because the function withdrawal() overrides the pure virtual func­ tion in the base class.

A subclass of an abstract class can remain abstract, however. Consider the following classes:

288 Part IV: Inheritance

class Display

{

public:

virtual void initialize( ) = 0; virtual void write(char *pString) = 0;

};

class SVGA : public Display

{

// override both member functions with “real” functions virtual void initialize( );

virtual void write(char *pString); };

class HWVGA : public Display

{

// override the only function we know how to up until now virtual void write(char *pString);

};

class ThreedVGA : public HWVGA

{

virtual void initialize( ); };

void fn( )

{

SVGA mc; ThreedVGA vga;

// ...what the function chooses to do from here...

}

The class Display, intended to represent video PC displays, has two pure virtual functions: initialize() and write(). You can’t implement either function for adapters in general. The different types of video cards do not ini­ tialize or write in the same way.

One of the subclasses, SVGA, is not abstract. This is a particular type of video adapter that the programmer knows how to program. Therefore, the class SVGA has overridden both initialize() and write() appropriately for this adapter.

HWVGA, another one of the subclasses, is also not abstract. Here again, the programmer knows how to program the accelerated VGA adapter hardware. In this case, however, a level of abstraction is between the generic Display and the specific case of the ThreedVGA display, which represents the special 3-D hardware display cards.

For this discussion, assume that all hardware-accelerated VGA cards are writ­ ten to in the same way, but that each must be initialized in its own way. (This