Chapter 3

Planning and Building Objects

IN THIS CHAPTER

check Recognizing objects so that you can create classes

check Encapsulating classes into self-contained capsules

check Building hierarchies of classes through inheritance

Step outside for a moment and look down. What is the thing you are standing on? (Hint: It’s giant, it’s made of rock and sand and stone and molten lava, and it’s covered with oceans and land.) The answer? A thing! (Even a planet is a thing.) Now go back inside. What’s the thing that you opened — the thing with a doorknob? It’s a thing, too! It’s a slightly different kind of thing, but a thing nevertheless. What are you standing in inside? Okay, you get the idea. Everything you can imagine is a thing — or, to use another term, an object.

Over the years, researchers in the world of computer programming have figured out that one of the better ways to program computers is to divide whatever it is you’re trying to model into a bunch of objects. These objects have methods (capabilities) and properties (characteristics). (Eventually they have relationships, but that comes later.)

In this chapter, you see how to make use of objects to create a software application. In the process, you get to twist some of the nuts and bolts of C++ that relate to objects and get tips on how to get the most out of them.

Remember You don’t have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the \CPP_AIO4\BookII\Chapter03 folder of the downloadable source. See the Introduction for details on how to find these source files.

Recognizing Objects

Think of an object as anything that a computer can describe. Just as physical things have characteristics, such as size, weight, and color, objects in an application can have properties — say, a particular number of accounts, an engine, or even other objects that it contains., A car, for example, contains engines, doors, and other objects.

Further, just as you can use real-world objects in certain ways because they have particular capabilities, an object in an application can use methods. For example, it might be able to withdraw money or send a message or connect to the Internet.

Here’s an example of modeling an object by thinking about how it’s put together and how you use it. Outside, in front of your house, you might see a mailbox. That mailbox is an object. A mailbox is a useful device. You can receive mail, and depending on the style (kind) of mail, you can send mail. (The style of mail is important — you can send a letter because you know how much postage to attach, but you can’t send a package because the amount of postage is unknown.) Those are the mailbox’s methods. And what about its characteristics? Different mailboxes come in different shapes, sizes, colors, and styles. So those are four properties. Now, some mailboxes, such as the kind often found at apartment buildings, are great big metal boxes with several little boxes inside, one for each apartment. The front has doors for each individual box, and the back has a large door for the mail carrier to fill the boxes with all those wonderful ads addressed to your alternative name: Resident.

In this case, you could think of the apartment mailbox as one big mailbox with lots of little boxes, or you could think of it as a big container for smaller mailboxes. In a sense, each of the little boxes has a front door that a resident uses, and the back of each one has an entry that the mail carrier uses. The back opens when the big container door opens.

So think about this: The mail carrier interacts with the container, which holds mailboxes. The container has a big door, and when that door opens, it exposes the insides of the small mailboxes inside, which open, too. Meanwhile, when a resident interacts with the system, he or she interacts with only his or her own particular box.

Take a look at Figures 3-1 and 3-2. Figure 3-1 shows the general look of the back of the mailbox container, where the mail carrier can open the container and put mail in all the different boxes. Figure 3-2 shows the front of the container, with the boxes open so that residents can take out the mail.

Schematic illustration of the outer object in this picture is a mailbox container.

FIGURE 3-1: The outer object in this picture is a mailbox container.

Schematic illustration of the smaller inner objects in the picture are the mailboxes.

FIGURE 3-2: The smaller inner objects in this picture are the mailboxes.

So far, there are two kinds of objects here: the container box and the mailboxes. But wait! There are multiple mailboxes. So, really, you have one container box and multiple mailboxes. But each mailbox is pretty much the same, except for a different lock and a different apartment number, right? In Figure 3-2, each box that’s open is an example of a single mailbox. The others are also examples of the type of object called mailbox. In Figure 3-2, you can see 16 examples of the objects classified as mailbox. In other words, Figure 3-2 shows 16 instances of the class called Mailbox. All those mailboxes are inside an instance of the class that you would probably call Mailboxes.

Tip There is no hard-and-fast rule about naming your classes. However, most developers use a singular name for objects and a plural name for collections. A single Mailbox object would appear as part of a Mailboxes collection. Using this naming convention makes it easier for other developers to understand how your code works. Of course, the most important issue is consistency. After you decide on a naming convention, use the same convention all the time.

Observing the Mailboxes class

What can you say about the Mailboxes collection object?

  • The Mailboxes collection contains 16 mailbox instances.
  • The Mailboxes collection object is 24 inches by 24 inches in front and back, and it is 18 inches deep.
  • When the carrier unlocks the mailboxes and pulls, its big door opens.
  • When the mailboxes’ big door opens, it exposes the insides of each contained mailbox.
  • When the mail carrier pushes on the door, the door shuts and relocks.

By using this list, you can discover some of the properties and methods of the Mailboxes collection. The following list shows its properties:

  • Width: 24 inches
  • Height: 24 inches
  • Depth: 18 inches
  • Mailboxes: 16 Mailbox objects inside

And here’s a list of some of the Mailboxes collection methods:

  • Open its door.
  • Give the mail carrier access to the mailboxes.
  • Close its door.

Think about the process of the carrier opening or closing the door. Here we seem to have a bizarre thing: The mail carrier asks the Mailboxes collection to close its door, and the door closes. That’s the way you need to look at modeling objects: Nobody does anything to an object. Rather, someone asks the object to do something, and the object does it itself.

For example, when you reach up to shove a slice of pizza into your mouth, your brain sends signals to the muscles in your arm. Your brain sends out the signals, and your arms move up, and so does the pizza. The point is that you make the command; then the arms carry it out, even though you feel like you’re causing your arms to do it.

Objects are the same way: They have their methods, and you tell them to do their job. You don’t do it for them. At least, that’s the way computer scientists view it. The more you think in this manner, the better you understand object-oriented programming.

Remember The Mailboxes collection contains 16 Mailbox objects. In C++, that means the Mailboxes collection has as properties 16 different Mailbox instances. These Mailbox instances could contain an array or some other collection, and most likely the array holds pointers to Mail instances within the Mailbox object.

Observing the Mailbox class

Consider the characteristics and capabilities of the Mailbox class. Each Mailbox has these properties:

  • Width: 6 inches
  • Height: 6 inches
  • Depth: 18 inches
  • Address: A unique integer

And each Mailbox has these methods:

  • Open its door.
  • Close its door.

Notice that the methods are from the perspective of the Mailbox, not the person opening the Mailbox.

Now think about the question regarding the address printed on the Mailbox. There are 16 different Mailbox objects, and each one gets a different number. So it’s possible to say this: The Mailbox class includes an address, which is an integer. Each instance of the Mailbox class gets its own number. The first may get 1, the second may get 2, and so on. So you have two concepts here for representing the mailboxes in code:

  • Mailbox class: This is the general description of a mailbox. It includes no specifics, such as the actual address. It simply states that each mailbox has an address.
  • Mailbox instance: This is the actual object. The Mailbox instance belongs to the class Mailbox. There can be any number of instances of the Mailbox class.

Think of the Mailbox class as a cookie cutter — or, in C++ terminology, the type. The Mailbox instance is an actual example of the class. In C++, you can create a variable of class Mailbox and set its Address integer to 1. Then you can create another variable of class Mailbox and set its Address integer to 2. Thus, you’ve created two distinct Mailbox objects, each of class Mailbox.

But all these Mailbox instances have a width of 6, a height of 6, and a depth of 18 inches. These properties are the same throughout the Mailboxes collection. Thus, you would probably not set those manually; instead, you would probably set them in the constructor for the class Mailbox. Nevertheless, the values of width, height, and depth go with each instance, not with the class; and the instances could, conceivably, each have their own width, height, and depth. However, when you design the class, you would put a stipulation in the class that these properties can’t be changed.

Finding other objects

If you are dealing with a Mailboxes instance and an instance of Mailbox, you can probably come up with some other classes. When you start considering the parts involved, you can think of the following objects:

  • Lock: Each Mailbox instance would have a Lock, and so would the Mailboxes instance.
  • Key: Each Lock instance would require one or more Key instances.
  • Mail: Each Mailbox instance can hold several Mail instances. The carrier puts these in the Mailbox instances, and the residents take them out.
  • LetterOpener: Some residents would use these to open the Mail.

So you now have four more types of objects (Lock, Key, Mail, and LetterOpener). But are these classes necessary? Their need depends on the application you’re building. In this case, you’re modeling the mailbox system simply as an exercise. Therefore, it’s possible to choose the desired classes. But if this were an actual application for a post office, for example, you would have to determine whether the classes are necessary for the people using the software. If the application is a training exercise for people learning to be mail carriers, the application may need more detail, such as the Key objects. If the application were a video game, it may need all the classes mentioned and even more.

Tip In deciding whether you need certain classes, you can follow some general rules. First, some classes are so trivial or simple that it doesn’t make sense to include them. For example, a letter opener serves little purpose beyond opening mail. If you’re designing a Mail class, you would probably have the method OpenEnvelope. Because some people would use a letter opener and others wouldn’t, you have little reason to pass into that method a LetterOpener instance. Therefore, you would probably not include a class as trivial as LetterOpener. But then again, when writing a game that involves a Mail instance, you may allow use of a LetterOpener instance, but not a Scissors instance, to open the letter.

Encapsulating Objects

People have come up with various definitions for what exactly object-oriented means. The phrase various definitions in the preceding sentence means that there aren’t simple discussions around a table at a coffeehouse about what the term means. Rather, there are outright arguments! One of the central points of contention is whether C++ is object-oriented. In such discussions, one of the words that usually pops up is encapsulation, which hides data values within the class and prevents unauthorized access to them. People who defend C++ as being object-oriented point out that it supports encapsulation.

Considering the Application Programming Interface

Encapsulation is an important concept because it helps you create easier-to-use, safer, and more reliable applications. In the world of computer programming, encapsulation refers to the process of creating a stand-alone object that can take care of itself and do what it must do while holding on to information. For example, to model a cash register, an application would encapsulate the cash register by putting everything about the register (its methods and properties) into a single class.

Remember To keep data within the class safe, you would make some methods and properties public (accessible through an Application Programming Interface, API) and others private (accessible only through the class). Some methods and properties can be protected, so derived classes could access them, but they still wouldn’t be public. The combination of public methods and properties used by other developers to access the class is the class’s API.

Understanding properties

In Chapter 1 of this minibook, you see how to build classes and instantiate objects from them. The examples in that chapter are straightforward, and all you really deal with are properties and methods. A property in Chapter 1 is essentially a variable, such as Color InkColor;. However, real-world classes work a little differently. You create a class member, which is actually the property from Chapter 1, and access it through methods that consist of the following:

  • Setter: A special method used to set (modify) the value of a property.
  • Getter: A special method used to get (read) the value of a property.

When viewed in this way, a property can consist of a setter (write-only), getter (read-only), or both (read/write). The property is never actually touched as part of the API. Here are the reasons you want to use this approach:

  • Using a getter, it’s possible to ensure that the value supplied by the caller is the right type, the correct length, and is in a specific range. You can also verify that the data doesn’t contain viruses and other nasty stuff.
  • Using a getter or a setter (depending on access), you can change the format of data from its internal representation to its external representation. For example, you could represent money as strings externally and floating-point values internally.
  • Employing properties can allow you to perform security checks and other measures to keep data safe.
  • Using getters and setters also makes it easier to set a breakpoint for debugging (discussed in Book 4 Chapter 2).
  • Increasing property functionality can make it possible to manage resources in various ways, such as allowing access only at given times (configurable by an administrator).

Methods also access properties, but in a different manner than properties do. When designing a cash register class, you’d probably have a property representing the total dollar amount that the register contains—the methods that use the class wouldn’t directly modify that value. Instead, they’d call various methods to perform transactions. One transaction might be Sale(). Another transaction might be Refund(); another might be Void(). These would be the capabilities of the register in the form of public methods, and they would modify the cash value inside the register, making sure that it balances with the sales and returns. If a method could just modify the cash value directly, the balance would get out of whack. Encapsulation, then, is this:

  • You combine the use of methods and properties to access class members, hiding some of them and making some accessible.
  • Some methods perform specific tasks that may access more than one property.
  • The accessible methods and properties together make up the API of the object.
  • When you create an object, you create one that can perform on its own. In other words, the users of the class tell it what to do (such as perform a sales transaction) by calling its methods or properties and supplying parameters, and the object does the work.

Choosing between private and protected

The cash amount would be a private or protected property. It would be hidden from the caller. As for which it would be, private or protected, that depends on whether you expect to derive new classes from the cash register class and whether you want these new classes to have access to the members.

In the situation of a cash register, you probably wouldn’t want other parts of the application to access the cash register total if you’re worried about security, so you might choose private. On the other hand, if you think that you’ll create derived classes that have added features involving the cash (such as automatically sending the money to a bank via an electronic transaction), you’d want the members to be protected. In general, developers often choose protected, rather than private, because they’ve been bitten too many times by using classes that have too many private members. In those cases, you’re unable to derive useful classes because everything is private!

Defining a process

The encapsulation process matters more than simply enclosing code in an easily accessed form. When you design objects and classes, you encapsulate your information into individual objects. If you keep the process in mind, you’ll be better off. Here are the things you need to do every time you design a class:

  • Encapsulate the information. Combine the information into a single entity that becomes the class. This single entity has properties representing its characteristics and methods representing its capabilities.
  • Clearly define the public interface of the class. Provide a set of properties and methods that are public, and make the class members either protected or private.
  • Write the class so that it knows how to do its own work. The class’s users should need only to call the methods in the public interface, and these public methods should be simple to use.
  • Think of your class as a black box. The object has an interface that provides a means so that others can use it. The class includes details of how it does its thing; users only care that it does it. In other words, the users don’t see into the class.
  • Never change the class interface after you publish the class. Many application errors occur when a developer changes how methods, properties, events, or access methods in the class work after publishing the class. If application developers rely on one behavior and the class developer introduces a new behavior, all applications that rely on the original behavior will break. You can always add to a class interface but never subtract from it or modify it. If you find that you must introduce a new behavior to Sale(), add the new behavior to a new method, Sale2().

Implementing properties

A common saying in object-oriented programming is that you should never make your properties public. The idea is that if users of the object can easily make changes to the object’s properties, a big mess could result. Previous sections mention properties and then talk about special methods as well. There are two methods of accessing property values in C++, and most developers today implement both when possible:

  • Getter/setter as a method: You can use separate getter and setter methods, such as setValue() and getValue(). This is the approach that you can easily use with all versions of C++ and is the only officially supported technique.
  • Property approach: Developers who have a background in other languages, such as C#, prefer the property approach, in which you have the object name, a dot, and then the property you want to change, such as MyObject.Value. The selection of getter or setter is automatic, based on context. To implement this approach, you must either use the correct C++ language product, such as Microsoft C++, or create a specialized class.

    Tip The article at https://www.codeproject.com/Articles/118921/C-Properties tells you about the Microsoft approach to creating properties, which involves creating a standard getter, setter, or both and then relying on __declspec() to define the property. The discussion at https://stackoverflow.com/questions/8368512/does-c11-have-c-style-properties describes a number of methods you can use with standards-based C++, including the creation of a template. The point is that you can use the property approach with C++, but it requires some additional work.

The ImplementProperties example, shown in Listing 3-1, demonstrates the process for working with read-only, read/write, and write-only properties for a class.

LISTING 3-1: Working with Properties

#include <iostream>

using namespace std;

class MyDog {
protected:
string _Name;
int _Weight = 300;
bool _IsHealthy = false;

public:
// Properties
string getName() {
return _Name;
}

int getWeight() {
return _Weight;
}
void setWeight(int Weight) {
if (Weight > 0)
_Weight = Weight;
}

void setIsHealthy(bool IsHealthy) {
if (_Weight > 200)
_IsHealthy = false;
else
_IsHealthy = IsHealthy;
}

// Methods
MyDog(string Name);
void DoDogRun();
};

MyDog::MyDog(string Name) {
if (Name.length() == 0)
throw "Error: Couldn't create MyDog!";

MyDog::_Name = Name;
}

void MyDog::DoDogRun() {
if (MyDog::_IsHealthy)
cout << MyDog::_Name << " is running!" << endl;
else if (MyDog::_Weight > 200)
cout << MyDog::_Name << " is too fat to run!" << endl;
else
cout << MyDog::_Name
<< " is unhealthy; see vet first!" << endl;
}

int main() {
MyDog *ThisDog;

try {
// Uncomment to generate an error.
//ThisDog = new MyDog("");

ThisDog = new MyDog("Fred");
} catch (const char *msg) {
cerr << msg << endl;
return -1;
}

cout << ThisDog->getName() << " needs exercise."
<< endl;
ThisDog->DoDogRun();

ThisDog->setWeight(100);
ThisDog->DoDogRun();

ThisDog->setIsHealthy(true);
ThisDog->DoDogRun();

delete ThisDog;
ThisDog = 0;

return 0;
}

The code begins by creating protected properties. The default dog doesn’t have a name, but it does weigh 300 pounds and is definitely unhealthy. The properties provide setter and getter code as needed. For example, you don’t want to change the dog’s name after you create it, but you do want to change its weight as needed. Keeping the dog’s health state a secret provides personal protections for the dog, so you can set it, but you can’t get it. Notice how you use the getters and setters to interact with the data. For example, you can’t set the dog’s weight to a negative amount.

Remember Because you can’t change the dog’s name after you create the dog, the constructor has to accept a name. Notice how this constructor code includes exception handling. If someone tries to create the object without supplying a name, the constructor will throw an exception and not create a new MyDog object. Consequently, when you create ThisDog, you must enclose it within a try…catch block, as shown in Listing 3-1. The error message shows the problem onscreen:

Error: Couldn't create MyDog!

At this point, you can interact with ThisDog in the same way that you interact with any other object. The example discovers that the poor dog needs exercise, but you can’t exercise the dog at first because he’s too fat. Even after losing weight, Fred needs to become healthy before going out for a good run. However, look at the setIsHealthy() code. If Fred weighs more than 200 pounds, the code ignores that the input value indicates that Fred still isn’t healthy. Here is the output from this example:

Fred needs exercise.
Fred is too fat to run!
Fred is unhealthy; see vet first!
Fred is running!

Building Hierarchies

One of the great powers in C++ is the capability to take a class and build new classes from it. When you use any of the available C++ libraries, such as the Standard C++ Library, you will probably encounter many classes — sometimes dozens of classes — that are all related to each other. Some classes are derived from other classes, although some classes are stand-alone. This gives programmers great flexibility. It’s good for a class library to be flexible because when you’re using a flexible library, you have many choices in the different classes you want to use.

Establishing a hierarchy

When you design a class, you have the option of deriving the class you’re creating from an original base class — creating a child/parent relationship. The new class inherits the capabilities and characteristics of the base class. Normally, the members that are public in the base class will remain public in the derived class. The members that are protected in the base class will remain protected in the derived class; thus, if you derive even further, those final classes will also inherit the protected members. Private members, however, live only in the base class.

Suppose you have a base class called FrozenFood, and from there you derive a class called FrozenPizza. From FrozenPizza, you then derive a class called DeepDishPizza. FrozenFood is at the top of the hierarchy. It includes various members common to all classes. Now suppose that the FrozenFood class has the following properties:

  • int Price (private): This is a private variable that represents the price of the product.
  • int Weight (protected): This is a protected variable that represents the weight of the product.

The FrozenFood class also has these methods:

  • constructor: The constructor is public and takes a price and a weight as parameters. It saves them in the Price and Weight properties, respectively.
  • GetPrice(): This is a public access method that returns the value in the private Price property.
  • GetWeight(): This is a public access method that returns the value in the protected Weight property.

To make this concept clearer, it helps to list these items in a box, putting the name of the class (FrozenFood) at the top of the box. Then the box has a horizontal line through it, and under that you list the properties. Under the properties, you have another line, and then a list of methods, as shown in Figure 3-3.

Schematic illustration of drawing a class by using a box divided into three horizontal sections.

FIGURE 3-3: You can draw a class by using a box divided into three horizontal sections.

Note that in this figure, you can describe the visibility of each property and method:

  • +: Public
  • –: Private
  • #: Protected

Technical stuff Even though Figure 3-3 is helpful in assisting anyone in visualizing a class and ultimately class relationships, it’s part of a technique called the Unified Modeling Language (UML) — a topic associated with software engineering and not discussed further in this book. If you’re interesting in learning more about UML, you can find tutorials at https://www.tutorialspoint.com/uml/index.htm and https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/. You can write great code without using UML and, today, most developers rely on it only when working on large projects that would be hard to manage otherwise.

Protecting members when inheriting

In C++, you have options for how you derive a class. To understand this, remember that when you derive a class, the derived class inherits the members from the base class. With the different ways to derive a class, you can specify whether those inherited members will be public, protected, or private in the derived class. Here are the options:

  • Public: When you derive a new class as public, all members that were public in the base class will remain public in this derived class.
  • Protected: When you derive a new class as protected, all members that were public in the base class will now be protected in this new class. This means the members that were public in the base class will not be accessible by users of this new class.
  • Private: When you derive a new class as private, all members in the base class that this new class can access will be private. This means that these members will not be accessible by any classes that you later derive from this new class or by users of the class.

Think of it as an order of diminishing accessibility: The highest access is public. When a member is public, users can access the member. The middle access is protected. Users cannot access protected members, but derived classes will have access to the protected members. The lowest access is private. Users cannot access private members, and derived classes can’t, either.

Remember To put protection during inheritance in perspective, consider the FrozenFood class and its children. When working with the FrozenPizza derived class, you see a combination of the members in FrozenFood and additional FrozenPizza members. However, only the methods in the FrozenFood portion of FrozenPizza can access the private members of the FrozenFood portion. Nevertheless, the methods in the FrozenFood portion of FrozenPizza and the private members of FrozenFood are part of the derived class.

When you derive a class as public, the base class portion of the derived class remains unchanged: Those items that were private remain in the base class portion; therefore, the derived class does not have access to them. Those that were protected are still protected, and those that were public are still public.

But when you derive a class as protected, the base class portion is different from the original base class: Its public members are now protected members of this derived class. The actual base class itself did not change; only the base class portion of the derived class becomes protected. Thus, the members that were public in the base class but are now protected in the derived class are not accessible to other methods and classes.

And finally, if you derive a class as private, the base class portion is again different from the original base class: All its members are now private. Because its members are private, any classes you derive from this newly derived class can’t access these members: They’re private. However, as before, the original base class itself didn’t change.

In C++, you specify the type of inheritance you want in the header line for the derived class. Look at the InheritedMembers example, shown in Listing 3-2. Notice the three classes at the top of the listing: FrozenFood, FrozenPizza, and DeepDishPizza. FrozenFood is the base class of FrozenPizza, and FrozenPizza is the base class of DeepDishPizza. Figure 3-4 shows this relationship using arrows to point toward the base class.

Schematic illustration of the arrows in this UML diagram point toward the base class.

FIGURE 3-4: The arrows in this UML diagram point toward the base class.

LISTING 3-2: Specifying the Access Levels of the Inherited Members

#include <iostream>

using namespace std;

class FrozenFood {
private:
int Price;
protected:
int Weight;
public:
FrozenFood(int APrice, int AWeight);
int GetPrice();
int GetWeight();
};

class FrozenPizza : public FrozenFood {
protected:
int Diameter;
public:
FrozenPizza(int APrice, int AWeight, int ADiameter);
void DumpInfo();
};

class DeepDishPizza : public FrozenPizza {
private:
int Height;
public:
DeepDishPizza(int APrice, int AWeight, int ADiameter,
int AHeight);
void DumpDensity();
};

FrozenFood::FrozenFood(int APrice, int AWeight) {
Price = APrice;
Weight = AWeight;
}

int FrozenFood::GetPrice() {
return Price;
}

int FrozenFood::GetWeight() {
return Weight;
}

FrozenPizza::FrozenPizza(int APrice, int AWeight,
int ADiameter) :
FrozenFood(APrice, AWeight) {
Diameter = ADiameter;
}

void FrozenPizza::DumpInfo() {
cout << "\tFrozen pizza info:" << endl;
cout << "\t\tWeight: " << Weight << " ounces" << endl;
cout << "\t\tDiameter: " << Diameter << " inches"
<< endl;
}

DeepDishPizza::DeepDishPizza(int APrice, int AWeight,
int ADiameter, int AHeight) :
FrozenPizza(APrice, AWeight,
ADiameter) {
Height = AHeight;
}

void DeepDishPizza::DumpDensity() {
// Calculate pounds per cubic foot of deep-dish pizza
cout << "\tDensity: ";
cout << Weight * 12 * 12 * 12 * 14 /
(Height * Diameter * 22 * 16);
cout << " pounds per cubic foot" << endl;
}

int main() {
cout << "Thin crust pepperoni" << endl;
FrozenPizza pepperoni(450, 12, 14);
pepperoni.DumpInfo();
cout << "\tPrice: " << pepperoni.GetPrice()
<< " cents" << endl;

cout << "Deep dish extra-cheese" << endl;
DeepDishPizza extracheese(650, 21592, 14, 3);
extracheese.DumpInfo();
extracheese.DumpDensity();
cout << "\tPrice: " << extracheese.GetPrice()
<< " cents" << endl;
return 0;
}

When you run Listing 3-2, you see the following output:

Thin crust pepperoni
Frozen pizza info:
Weight: 12 ounces
Diameter: 14 inches
Price: 450 cents
Deep dish extra-cheese
Frozen pizza info:
Weight: 21592 ounces
Diameter: 14 inches
Density: 35332 pounds per cubic foot
Price: 650 cents

The first five lines show information about the object of class FrozenPizza. The remaining lines show information about the object of class DeepDishPizza, including the fact that it weighs 21,592 ounces (which happens to be 1349.5 pounds), It has a density of 35,332 pounds per cubic foot.

The derivations are all public. Thus, the items that were public in FrozenFood are still public in FrozenPizza and DeepDishPizza. Note where the different information in the output comes from. The line Frozen pizza info: and the two lines that follow (Weight: and Diameter:) come from the public method DumpInfo(), which is a member of FrozenPizza. DumpInfo() is public in the FrozenPizza class. Since DeepDishPizza derives from FrozenPizza as public, DumpInfo() is also a public member of DeepDishPizza.

Try changing the header for DeepDishPizza from

class DeepDishPizza : public FrozenPizza

to

class DeepDishPizza : protected FrozenPizza

You’re changing the word public to protected. Make sure that you change the correct line. Compile and run the application. You see an error that looks similar to this one:

In function 'int main()':
error: 'void FrozenPizza::DumpInfo()' is inaccessible
error: within this context
error: 'FrozenPizza' is not an accessible base of 'DeepDishPizza'
error: 'int FrozenFood::GetPrice()' is inaccessible
error: within this context
error: 'FrozenFood' is not an accessible base of 'DeepDishPizza'

This message refers to the extracheese.DumpInfo(); line in main(). DumpInfo() is now a protected member of DeepDishPizza, thanks to the word protected in the class header. By putting the word protected in the class definition, you’re saying the inherited members that are currently public will instead be protected. Because the DumpInfo() member is protected, you can’t call it from main(). However, DumpInfo() is still public in the FrozenPizza class, so this call is fine:

pepperoni.DumpInfo();

Tip Note that you can double-click the error: within this context line to jump directly to the code that is trying to access the hidden member, rather than look at the initial error. Using this technique saves you time looking for the errant code.

Change the line back to a public inheritance, as it was in Listing 3-2: class DeepDishPizza : public FrozenPizza.

And now change the header of FrozenPizza so that it looks like this:

class FrozenPizza : private FrozenFood

Again, make sure to change the correct lines. Compile and run the application to see the following error:

In function 'int main()':|
error: 'void FrozenPizza::DumpInfo()' is inaccessible|
error: within this context|
error: 'FrozenPizza' is not an accessible base of 'DeepDishPizza'|
error: 'int FrozenFood::GetPrice()' is inaccessible|
error: within this context|
error: 'FrozenFood' is not an accessible base of 'DeepDishPizza'|

This error refers to the line inside DeepDishPizza::DumpDensity() where the code is trying to access the Weight member. The compiler doesn’t allow access now because the member, which was public in the original FrozenFood class, became private when it became a part of FrozenPizza. And because it’s private in FrozenPizza, the derived class DeepDishPizza can’t access it from within its own methods. Make sure to change back the header of FrozenPizza so that it looks like this: class FrozenPizza : public FrozenFood.

Overriding methods

One of the cool things about classes is that you can declare a method in one class, and then when you derive a new class, you can give that class a different version of the same method. This is called overriding the method. For example, if you have a class FrozenFood and a derived class FrozenPizza, you may want to include a method in FrozenFood called BakeChemistry(), which modifies the food when it’s baked. Because all foods are different, the BakeChemistry() method would be different for each class derived from FrozenFood.

In C++, you can provide a different version of the method for the different derived classes by adding the word virtual before the method name in the base class declaration, as in this line of code:

virtual void BakeChemistry();

This line is the prototype inside the class definition. Later, you would provide the code for this method. In the class for your derived class, you would then just put the method prototype, without the word virtual:

void BakeChemistry();

And as before, you would include the code for the method later on. For example, you might have something like the following example. First, here are the classes:

class FrozenFood {
private:
int Price;
protected:
int Weight;
public:
FrozenFood(int APrice, int AWeight);
int GetPrice();
int GetWeight();
virtual void BakeChemistry();
};

class FrozenPizza : public FrozenFood {
protected:
int Diameter;
public:
FrozenPizza(int APrice, int AWeight, int ADiameter);
void DumpInfo();
void BakeChemistry();
};

You can see the word virtual in the FrozenFood class, and then you see the method declaration again in the FrozenPizza class. Now, here are the BakeChemistry() methods:

void FrozenFood::BakeChemistry() {
cout << "Baking, baking, baking!" << endl;
}

void FrozenPizza::BakeChemistry() {
cout << "I'm getting crispy!" << endl;
}

Note that the word virtual doesn’t appear in front of the methods; it appears only in the class declaration. Now, whenever you make an instance of each class and call BakeChemistry() for each instance, you call the one for the given class. Consider the following two lines of code:

FrozenPizza pepperoni(450, 12, 14);
pepperoni.BakeChemistry();

Because pepperoni is an instance of FrozenPizza, this code calls the BakeChemistry() for the FrozenPizza class, not for the FrozenFood class. You may not want any code in your base class for the BakeChemistry() method. If so, you can do this:

virtual void BakeChemistry() {}

Remember The reason to take this approach is that you don’t need code in the base class, but you do want code in the derived classes, and you want them to be different versions of the same code. The idea, then, is to provide a basic, default set of code that the classes inherit if they don’t override the method. And sometimes, that basic, default set of code is simply nothing. So you would put only an open brace and a closing brace, and you can do that inside the class itself:

class FrozenFood
{
private:
int Price;
protected:
int Weight;
public:
FrozenFood(int APrice, int AWeight);
int GetPrice();
int GetWeight();
virtual void BakeChemistry() {}
};

Specializing with polymorphism

Suppose you have a method called Bake() and you want it to take as a parameter a FrozenFood instance. If you derive FrozenPizza from FrozenFood and then derive DeepDishPizza from FrozenPizza, by the “is a” rule, objects of the class FrozenPizza and DeepDishPizza are both examples of FrozenFood objects. This is true in general: If you have a class called Base and you derive from that a class called Derived, instances of class Derived are also instances of class Base. Therefore, if you have a method called Bake() and you declare it as follows, you are free to pass to this method a FrozenFood instance or to pass an instance of any class derived from FrozenFood, such as FrozenPizza or DeepDishPizza:

void Bake(FrozenFood *)
{
cout << "Baking" << endl;
}

Suppose that in this Bake() method, you set the oven temperature to a fixed amount, turn on the oven, and then cook the food. Every food behaves differently in the oven. For example, a deep-dish frozen pizza might rise and become thicker, but a regular frozen pizza will become crispier but not get any thicker.

You don’t really want to put all the different food types inside the Bake() method, with if statements for each food type. Instead, you can put the actual baking chemistry in the class for the food itself. The FrozenPizza would have its own BakeChemistry() method, and the DeepDishPizza would also have its own BakeChemistry() method. Then the Bake() method would call BakeChemistry() for whatever object it receives as a parameter. C++ knows how to do this because of the virtual methods. The Bake() method doesn’t even know or care what type of FrozenFood it receives. It just calls BakeChemistry() for whatever object it receives. And when you modify the application by writing a new class derived from FrozenFood and give it its own BakeChemistry() method, you can pass an instance of this class to Bake(), without even having to modify Bake(). This whole process is called polymorphism.

Remember Polymorphism is one of the most powerful aspects of object-oriented programming. The idea is that you can expand and enhance your application by adding new classes derived from a common base class. Then you have to make very few (if any) modifications to the rest of your application. Because you used virtual methods and polymorphism, the rest of your application automatically understands the new class you created. In essence, you are able to snap in the new class, and the application will run just fine.

Getting abstract about things

When you create a base class with a virtual method and then derive other classes, you may want to override the virtual method in all the derived classes. Furthermore, you may want to make sure that nobody ever creates an instance of the base class. You do this because the base class might contain basic things that are common to all the other classes, but the class itself doesn’t make much sense as an instance. For example, no one would want you to go to the store and pick up a frozen food without specifying the sort of frozen food to get. Consequently, it doesn’t make much sense to have an instance of a class called FrozenFood.

Remember Philosophers have a word to describe such things: abstract. The class FrozenFood is abstract; it doesn’t make sense to create an instance of it. In C++, you can make a class abstract, but when you do, the compiler won’t allow you to make any instances of the class.

In C++, you don’t actually specify that the class is abstract. The word abstract doesn’t appear in the language. To specify that the class is abstract, you add at least one virtual method that has no code. But instead of just putting an empty code block, as in {}, you follow the method prototype in the class definition with = 0 (called the pure specifier, which makes the method a pure virtual method or an abstract virtual method, depending on which you prefer), as in

class FrozenFood
{
private:
int Price;
protected:
int Weight;
public:
FrozenFood(int APrice, int AWeight);
int GetPrice();
int GetWeight();
virtual void BakeChemistry() = 0;
};

In this class definition, the method BakeChemistry() has = 0 after it (but before the semicolon). The = 0 transforms the virtual method into an abstract virtual method, which transforms the class into an abstract class.

After you create an abstract class, you must also modify the derived classes by overriding the abstract virtual method. Otherwise, the derived classes will also be abstract. When your class is abstract, you can’t create instances of it. To override the abstract virtual method, you override as you would with any virtual method. This class includes a method that overrides the BakeChemistry() method:

class FrozenPizza : public FrozenFood
{
protected:
int Diameter;
public:
FrozenPizza(int APrice, int AWeight, int ADiameter);
void DumpInfo();
void BakeChemistry();
};

Then you provide the code for the BakeChemistry() method, as in

void FrozenPizza::BakeChemistry()
{
cout << "I'm getting crispy under this heat!" << endl;
}

There’s nothing magical about defining the override method, but you are required to override it if you want to create an instance of this class.