Chapter 1

Working with Classes

IN THIS CHAPTER

check Understanding objects and classes

check Becoming familiar with methods and properties

check Making parts of a class public, private, and protected

check Using constructors and destructors

check Building hierarchies of classes

Back in the early 1990s, the big buzzword in the computer world was object-oriented. For anything to sell, it had to be object-oriented. Programming languages were object-oriented. Software applications were object-oriented. Computers were object-oriented. Unfortunately, object-oriented was simply a cool catchphrase at the time that meant little in real terms. Often, ideas begin poorly formed and gain resolution as people work to implement the idea in the real world.

Now it’s possible to explore what object-oriented really means and how you can use it to organize your C++ applications. In this chapter, you discover object-oriented programming and see how you can do it in C++. Although people disagree on the strict definition of object-oriented, in this book it means programming with objects and classes.

Understanding Objects and Classes

Consider a pen, a regular, old pen. Here’s what you can say about it:

  • Ink Color: Black
  • Shell Color: Light gray
  • Cap Color: Black
  • Style: Ballpoint
  • Length: Six inches
  • Brand: Paper Mate
  • Ink Level: 50 percent full
  • Capability #1: Write on paper
  • Capability #2: Break in half
  • Capability #3: Run out of ink

Now, look around for other things, such as a printer. Here’s a description of a printer:

  • Kind: Laser
  • Brand: HP
  • Model: MFP M479fdw
  • Ink Color: Color
  • Case Color: Cream
  • Input trays: One
  • Output trays: One
  • Connection: Ethernet/Wi-Fi/ Wi-Fi Direct
  • Capability #1: Reads print job requests from the device
  • Capability #2: Prints on sheets of paper
  • Capability #3: Prints a test page
  • Capability #4: Needs the toner cartridges replaced when empty

These lists describe the objects you might see. They provide dimensions, color, model, brand, and other details. The lists also describe what the objects can do. The pen can break in half and run out of ink. The printer can take print jobs, print pages, and have its cartridges replaced.

When describing what objects can do, you carefully write it from the perspective of the object itself, not from the perspective of the person using the object. A good way to name the capability is to test it by preceding it with the words “I can” and see if it makes sense. Thus, because “I can write on paper” works from the perspective of a pen, the list contains write on paper for one of the pen’s capabilities. But is seeing all the objects in the universe possible, or are some objects hidden? Certainly, some objects are physical, like atoms or the dark side of the moon, and you can’t see them. But other objects are abstract. For example, you may have a credit card account. What is a credit card account, exactly? A credit card account is abstract because you can’t touch it — it has no physical presence. The following sections of the chapter examine various kinds of objects: those with physical representations and those that are abstract.

Classifying classes and objects

When you pick up a pen, you can ask somebody, “What type of object is this an instance of?” Most people would probably say, “a pen.” In computer programming, instead of using type of object, you say class. This thing in your hand belongs to the pen class. Now if you point to the object parked out in the driveway and ask, “What class does that belong to?” the answer is, “class Car.” Of course, you could be more specific. You may say that the object belongs to class 2020 Ford Taurus.

When you see a pen, you might ask what class this object belongs to. If you then pick up another pen, you see another example of the same class. One class; several examples. If you stand next to a busy street, you see many examples of the class called car. Or you may see many examples of the class Ford Explorer, a few instances of the class Toyota Corolla, and so on. It depends on how you classify those objects roaring down the road. Regardless, you likely see several examples of any given class.

So when you organize things, you specify a class, which is the type of object. And when you’re ready, you can start picking out examples (or instances) of the class. Each class may have several instances. Some classes have only one instance. That’s a singleton class. For example, at any given time, the class United States President would have one instance.

Describing methods and data

If you choose a class, you can describe its characteristics. However, because you’re describing only the class characteristics, you don’t actually specify them. You may say the pen has an ink color, but you don’t actually say what color. That’s because you don’t yet have an example of the class Pen. You have only the class itself. When you finally find an example, it may be one color, or it may be another. So, if you’re describing a class called Pen, you may list the characteristics presented in the introduction to this section.

You don’t specify ink color, shell color, length, or any of these properties (terms that describe the class) as actual values. You’re listing only general characteristics for all instances of the class Pen. That is, every pen has these properties. But the actual values for these properties might vary from instance to instance. One pen may have a different ink color from another, but both might have the same brand. Nevertheless, they are both separate instances of the class Pen.

After creating an instance of class Pen, you can provide values for the properties. For example, Table 1-1 lists the property values of three actual pens.

TABLE 1-1 Specifying Property Values for Instances of Class Pen

Property Name

First Pen

Second Pen

Third Pen

Ink Color

Blue

Red

Black

Shell Color

Grey

Red

Grey

Cap Color

Blue

Black

Black

Style

Ballpoint

Fountain

Felt-tip

Length

5.5 inches

5 inches

6 inches

Brand

Office Depot

Parker

Paper Mate

Ink Level

30%

60%

90%

In Table 1-1, the first column holds the property names. The second column holds property values for the first pen. The third column holds the property values for the second pen, and the final column holds the property values for the third pen. All the pens in the class share properties. But the values for these properties may differ from pen to pen. When you instantiate (build or create) a new Pen, you follow the list of properties, giving the new pen instance its own values. You may make the shell purple with yellow speckles, or you may make it transparent. But you would give it a shell that has some color, even if that color is transparent.

In Table 1-1, you didn’t see a list of methods (ways of interacting with the Pen class to exercise its capabilities). But all these pens have the same methods:

  • Method #1: Write on paper
  • Method #2: Break in half
  • Method #3: Run out of ink

Unlike properties, methods don’t change from instance to instance. They are the same for each class.

Tip When you describe classes to build a computer application using a class, you are modeling. In the preceding examples, you modeled a class called Pen. In the following section, you implement this model by writing an application that mimics a pen using the Pen class.

Tip If you work with enums (the code form of enumerations), you need to decide what to name your new type. For example, you can choose MyColor or MyColors. Many people, when they write a line such as enum MyColor {blue, red, green, yellow, black, beige};, make the name plural (MyColors) because this is a list of colors. It’s best to make the term singular, as in MyColor, because you use only one color at a time. When you declare a variable, it makes more sense: MyColor inkcolor; would mean that inkcolor is a color — not a group of colors.

Implementing a class

To implement a class in C++, you use the keyword class. And then you add the name of the class, such as Pen. You then add an open brace, list your properties and methods, and end with a closing brace.

Tip Most people capitalize the first letter of a class name in C++, and if their class name is a word, they don’t capitalize the remaining letters. Although you don’t have to follow this rule, many people do. You can choose any name for a C++ class provided it is not a C++ keyword; it consists only of letters, digits, and underscores; and it does not start with a number.

The PenClass example, shown in Listing 1-1, contains a C++ class description that appears inside the Pen.h header file. (See Book 1, Chapter 7, for information on how to put code in a header file.) Review the header file, and you see how it implements the different characteristics. The properties of a header file are just like variables: They have a type and a name. The methods are implemented using functions. All this code goes inside curly brackets and is preceded by a class header. The header gives the name of the class. And, oh yes, the word public is stuck in there, and it has a colon after it. The “Accessing members,” section later in this chapter explains the word public. By itself, this code isn’t very useful, but you put it to use in Listing 1-2, an application that you can actually compile and run.

LISTING 1-1: Pen.h Contains the Class Description for Pen

#ifndef PEN_H_INCLUDED
#define PEN_H_INCLUDED
using namespace std;
enum Color {
blue,
red,
black,
clear,
grey
};

enum PenStyle {
ballpoint,
felt_tip,
fountain_pen
};

class Pen {
public:
Color InkColor;
Color ShellColor;
Color CapColor;
PenStyle Style;
float Length;
string Brand;
int InkLevelPercent;

void write_on_paper(string words) {
if (InkLevelPercent <= 0) {
cout << "Oops! Out of ink!" << endl;
}
else {
cout << words << endl;
InkLevelPercent = InkLevelPercent - words.length();
}
}

void break_in_half() {
InkLevelPercent = InkLevelPercent / 2;
Length = Length / 2.0;
}

void run_out_of_ink() {
InkLevelPercent = 0;
}
};
#endif // PEN_H_INCLUDED

Remember When you write a class, you always end it with a semicolon. Write that down on a sticky note and hang it on the refrigerator. The effort spent in doing this will be well worth avoiding the frustration of wondering why your code won’t compile.

Remember In a class definition, you describe the characteristics and capabilities (that is, supply the properties and methods, respectively).

Note in Listing 1-1, earlier in this chapter, that the methods access the properties. However, we said that these variables don’t have values yet, because this is just a class, not an instance of a class. How can that be? When you create an instance of this class, you can give values to these properties. Then you can call the methods. And here’s the really great part: You can make a second instance of this class and give it its own values for the properties. Yes, the two instances will each have their own sets of properties. And when you run the methods for the second instance, these functions operate on the properties for the second instance. Isn’t C++ smart? Now look at Listing 1-2. This is a source file that uses the header file in Listing 1-1. In this code, you see the Pen class in action.

LISTING 1-2: main.cpp Contains Code That Uses the Class Pen

#include <iostream>
#include "Pen.h"

using namespace std;

int main() {
Pen FavoritePen;
FavoritePen.InkColor = blue;
FavoritePen.ShellColor = grey;
FavoritePen.CapColor = blue;
FavoritePen.Style = ballpoint;
FavoritePen.Length = 5.5;
FavoritePen.Brand = "Office Depot";
FavoritePen.InkLevelPercent = 30;

Pen WorstPen;
WorstPen.InkColor = red;
WorstPen.ShellColor = red;
WorstPen.CapColor = black;
WorstPen.Style = fountain_pen;
WorstPen.Length = 5.0;
WorstPen.Brand = "Parker";
WorstPen.InkLevelPercent = 60;

cout << "This is my favorite pen" << endl;
cout << "Color: " << FavoritePen.InkColor << endl;
cout << "Brand: " << FavoritePen.Brand << endl;
cout << "Ink Level: " << FavoritePen.InkLevelPercent
<< "%" << endl;
FavoritePen.write_on_paper("Hello I am a pen");
cout << "Ink Level: " << FavoritePen.InkLevelPercent
<< "%" << endl;

return 0;
}

There are two variables of class Pen: FavoritePen and WorstPen. To access the properties of these objects, you type the name of the variable holding the object, a dot (or period), and then the property name. For example, to access the InkLevelPercent member of WorstPen, you type:

WorstPen.InkLevelPercent = 60;

Remember, WorstPen is the variable name, and this variable is an object. It is an object or an instance of class Pen. This object has various properties, including InkLevelPercent.

You can also run some of the methods that are in these objects. This code calls:

FavoritePen.write_on_paper("Hello I am a pen");

This called the function write_on_paper() for the object FavoritePen. Look at the code for this function, which is in the header file, Listing 1-1:

void write_on_paper(string words) {
if (InkLevelPercent <= 0) {
cout << "Oops! Out of ink!" << endl;
}
else {
cout << words << endl;
InkLevelPercent = InkLevelPercent - words.length();
}
}

This function uses the variable called InkLevelPercent. But InkLevelPercent isn’t declared in this function. The reason is that InkLevelPercent is part of the object and is declared in the class. Suppose you call this method for two different objects, as in the following:

FavoritePen.write_on_paper("Hello I am a pen");
WorstPen.write_on_paper("Hello I am another pen");

The first of these lines calls write_on_paper() for the FavoritePen object; thus, inside the code for write_on_paper(), the InkLevelPercent refers to InkLevelPercent for the FavoritePen object. It looks at and possibly decreases the variable for that object only. But WorstPen has its own InkLevelPercent property, separate from that of FavoritePen. So in the second of these two lines, write_on_paper() accesses and possibly decreases the InkLevelPercent that lives inside WorstPen. In other words, each object has its own InkLevelPercent. When you call write_on_paper(), the function modifies the property based on which object you are calling it with. The first line calls it with FavoritePen. The second calls it with WorstPen. When you run this application, you see the following output:

This is my favorite pen
Color: 0
Brand: Office Depot
Ink Level: 30%
Hello I am a pen
Ink Level: 14%

You should notice something about the color line. Here’s the line of code that writes it:

cout << "Color: " << FavoritePen.InkColor << endl;

This line outputs the InkColor member for FavoritePen. But what type is InkColor? It’s the new Color enumerated type. But something is wrong. It printed 0 despite being set as follows:

FavoritePen.InkColor = blue;

The code sets it to blue, not 0. Unfortunately, that’s the breaks with using enum. You can use it in your code, but under the hood, it just stores numbers. When printed, you get a number. The compiler chooses the numbers for you, and it starts the first entry in the enum list as 0, the second as 1, then 2, then 3, and so on. Thus, blue is stored as 0, red as 1, black as 2, clear as 3, and grey as 4. Fortunately, people have found a way to create a new class that handles the enum for you (that is, it wraps around the enum), and then you can print what you really want: blue, red, black, clear, and grey. Book 2, Chapter 2 has tips on how to do this astounding feat.

Remember Remember that you can create several objects (also called instances) of a single class. Each object gets its own properties, which you declare in the class. To access the members of an object, you use a period, or dot.

Separating method code

When you work with functions, you can either make sure that the code to your function is positioned before any calls to the function, or you can use a forward reference, also called a function prototype. Book 1, Chapter 6 discusses this feature.

When you work with classes and methods, you have a similar option. Most C++ programmers prefer to keep the code for their methods outside the class definition. The reason for placing them outside is to make the code easier to read; you don’t end up with a single, huge block of code that is incredibly difficult to follow. In addition, someone using the class may not care about how the methods work, so keeping things simple is the best option. The class definition contains only method prototypes, or, at least, mostly method prototypes. If the method is one or two lines of code, people may leave it in the class definition.

When you use a method prototype in a class definition, you write the prototype by ending the method header with a semicolon where you would normally have the open brace and code. If your method looks like this:

void break_in_half() {
InkLevelPercent = InkLevelPercent / 2;
Length = Length / 2.0;
}

a method prototype would look like this:

void break_in_half();

After you write the method prototype in the class, you write the method code again outside the class definition. However, you need to doctor it up just a bit. In particular, you need to throw in the name of the class, so that the compiler knows which class this method goes with. The following is the same method described earlier, but with the class information included. You separate the class name and method name with a scope resolution operator (::) that links the method to the class:

void Pen::break_in_half() {
InkLevelPercent = InkLevelPercent / 2;
Length = Length / 2.0;
}

You put the method after your class definition. And you would want to put the method code inside one of your source code files if your class definition is in a header file.

Tip You can use the same method name in different classes. As are variables in different functions, method names are associated with a particular class using the scope resolution operator. Although you don’t want to go overboard on duplicating method names, if you feel a need to, you can certainly do it without a problem. For example, toString() is a common method name and you often see it provided with a wide range of classes in your application.

The PenClass2 example, shown in Listings 1-3 and 1-4, contains the modified version of the Pen class that appeared earlier in this chapter in Listing 1-1. You can use these two files together with Listing 1-2, which hasn’t changed.

LISTING 1-3: Using Method Prototypes with the Modified Pen.h file

#ifndef PEN_H_INCLUDED
#define PEN_H_INCLUDED

using namespace std;
enum Color {
blue,
red,
black,
clear,
grey
};

enum PenStyle {
ballpoint,
felt_tip,
fountain_pen
};

class Pen {
public:
Color InkColor;
Color ShellColor;
Color CapColor;
PenStyle Style;
float Length;
string Brand;
int InkLevelPercent;
void write_on_paper(string words);
void break_in_half();
void run_out_of_ink();
};

#endif // PEN_H_INCLUDED

LISTING 1-4: Containing the Methods for Class Pen in the New Pen.cpp File

#include <iostream>
#include "Pen.h"

using namespace std;

void Pen::write_on_paper(string words) {
if (InkLevelPercent <= 0) {
cout << "Oops! Out of ink!" << endl;
}
else {
cout << words << endl;
InkLevelPercent = InkLevelPercent - words.length();
}
}

void Pen::break_in_half() {
InkLevelPercent = InkLevelPercent / 2;
Length = Length / 2.0;
}

void Pen::run_out_of_ink() {
InkLevelPercent = 0;
}

All the functions from the class are now in a separate source (.cpp) file. The header file now just lists prototypes and is a little easier to read. The source file includes the header file at the top. That’s required; otherwise, the compiler won’t know that Pen is a class name, and it will get confused (as it so easily can).

The parts of a class

Here is a summary of the parts of a class and the different ways classes can work together:

  • Class: A class is a type. It includes properties and methods. Properties describe the class, and methods describe its behaviors.
  • Object: An object is an instance of a class. Think of the class as a blueprint and the object as the building created from the blueprint. You need only one blueprint to build multiple buildings of precisely the same type. Each building is an instance of that blueprint.
  • Class definition: The class definition describes the class. It starts with the word class, and then has the name of the class, followed by an open brace and closing brace. Inside the braces are the members of the class.
  • Property: A property is a characteristic in a class, such as a color, style, or other descriptive element. You list the properties inside the class (normally before any methods, but there is no rule that says you must do so). Each instance of the class gets its own copy of each property.
  • Method: A method is a capability of a class — some task that the class can perform. As with properties, you list methods inside the class. When you call a method for a particular instance, the method accesses the properties for the instance.

When you divide the class, you put part in the header file and part in the source code file. The following list describes what goes where:

  • Header file: Put the class definition in the header file. Properties appear as part of the class definition within the header. You can include the method code inside the class definition if it’s a short method. Most people prefer not to put any method code longer than a line or two in the header — in fact, many don’t put any method code at all in the header. You may want to name the header file the same as the class but with an .h or .hpp extension. Thus, the class Pen, for instance, might be in the file Pen.h.
  • Source file: If your class has methods, and you didn’t put the code in the class definition, you need to put the code in a source file. When you do, precede the function name with the class name and the scope resolution operator (::). If you named the header file the same as the class, you probably want to name the source file the same as the class as well but with a .cpp extension.

Working with a Class

Many handy tricks are available for working with classes. In this section, you explore several clever ways of working with classes, starting with the way you can hide certain parts of your class from other functions that are accessing them.

Accessing members

When you work with an object in real life, there are often parts of the object that you interact with and other parts that you don’t. For example, when you use the computer, you type on the keyboard but don’t open the box and poke around with a wire attached to a battery. For the most part, the stuff inside is off-limits except when you’re upgrading it.

In object terminology, the words public and private refer to properties and methods. When you design a class, you might want to make some properties and methods freely accessible by class users. You may want to keep other members tucked away. A class user is the part of an application that creates an instance of a class and calls one of its methods. In Listing 1-2, earlier in the chapter, main() is a class user. If you have a function called FlippityFlop() that creates an instance of your class and does a few things to the instance, such as change some its properties, FlippityFlop() is a class user. In short, a user is any function that accesses your class.

When designing a class, you may want only specific users calling certain methods. You may want to keep other methods hidden away, to be called only by other methods within the class. Suppose you’re writing a class called Oven. This class includes a method called Bake(), which takes a number as a parameter representing the desired oven temperature. Now you may also have a method called TurnOnHeatingElement() and one called TurnOffHeatingElement().

Here’s how it would work. The Bake() method starts out calling TurnOnHeatingElement(). Then it keeps track of the temperature, and when the temperature is correct, it calls TurnOffHeatingElement(). You wouldn’t want somebody walking in the kitchen and calling the TurnOnHeatingElement() method without touching any of the dials, only to leave the room as the oven gets hotter and hotter with nobody watching it. You allow the users of the class to call only Bake(). The other two methods, TurnOnHeatingElement() and TurnOffHeatingElement(), are reserved for use only by the Bake() function.

Remember You bar users from calling functions by making specific functions private. Functions that you want to allow access to you make public. After you design a class, if you write a function that instantiates an object based on that class that tries to call one of an object’s private methods, you get a compiler error when you try to compile it. The compiler won’t allow you to call it.

The OvenClass example, shown in Listing 1-5, defines a sample Oven class and a main() that uses it. Look at the class definition. It has two sections: one private and the other public. The code for the functions appears after the class definition. The two private functions don’t do much other than print a message. (Although they’re also free to call other private functions in the class.) The public function, Bake(), calls each of the private functions, because it’s allowed to.

LISTING 1-5: Using the Public and Private Words to Hide Parts of Your Class

#include <iostream>

using namespace std;

class Oven {
private:
void TurnOnHeatingElement();
void TurnOffHeatingElement();
public:
void Bake(int Temperature);
};

void Oven::TurnOnHeatingElement() {
cout << "Heating element is now ON! Be careful!" << endl;
}

void Oven::TurnOffHeatingElement() {
cout << "Heating element is now off. Relax!" << endl;
}

void Oven::Bake(int Temperature) {
TurnOnHeatingElement();
cout << "Baking!" << endl;
TurnOffHeatingElement();
}

int main() {
Oven fred;
fred.Bake(875);
return 0;
}

When you run this application, you see some messages:

Heating element is now ON! Be careful!
Baking!
Heating element is now off. Relax!

Nothing too fancy here. Now if you tried to include a line in your main() such as the one in the following code, where you call a private function

fred.TurnOnHeatingElement();

you see an error message telling you that you can’t do it because the function is private. In Code::Blocks, you see this message:

error: 'void Oven::TurnOnHeatingElement()' is private

Tip When you design your classes, consider making all the functions private by default, and then only make those public that you want users to access. Some people, however, prefer to go the other way around: Make them all public, and only make those private that you are sure you don’t want users to access. There are good arguments for either approach; however, the preference in this book is to make public only what must be public. This approach minimizes the risk of some other application that’s using that class creating errors by calling things the programmer doesn’t really understand.

Tip You don’t necessarily need to list the private members first followed by the public members. You can put the public members first if you prefer. Some people put the public members at the top so they see them first. That makes sense. Also, you can have more than one private section and more than one public section. For example, you can have a public section, a private section, and then another public section, as in the following code:

class Oven {
public:
void Bake(int Temperature);
private:
void TurnOnHeatingElement();
void TurnOffHeatingElement();
public:
void Broil();
};

Using classes and raw pointers

This and other sections of the chapter discuss the use of raw pointers with objects. In the “Understanding the Changes in Pointers for C++ 20” section of Book 1, Chapter 8, you discover that there are other pointer types, including smart and optional pointers. Because most code still relies on raw pointers to work with objects, the majority of this chapter focuses on their use.

As with any variable, you can have a pointer variable that points to an object. As usual, the pointer variable’s type must match the type of the class. This creates a pointer variable that points to a Pen instance:

Pen *MyPen;

The variable MyPen is a pointer, and it can point to an object of type Pen. The variable’s own type is pointer to Pen, or in C++ notation, Pen *. Because you’re always working with pointers when interacting with objects, you leave ptr off the variable name to save typing time and focus attention on the variable’s purpose, which is to serve as your personal pen.

Remember A line of code like Pen *MyPen; creates a variable that serves as a pointer to an object. But this line, by itself, does not actually create an instance. By itself, it points to nothing. To create an instance, you have to call new. This is a common mistake among C++ programmers; sometimes people forget to call new and wonder why their applications crash.

After you create the variable MyPen, you can create an instance of class Pen and point MyPen to it using the new keyword, like so:

MyPen = new Pen;

Or you can combine both Pen *MyPen; and the preceding line:

Pen *MyPen = new Pen;

Now you have two variables: You have the actual object, which is unnamed and sitting on the heap. (See the “Heaping and Stacking the Variables” section of Book 1, Chapter 8, for more information on pointers and heaps.) You also have the pointer variable, which points to the object: two variables working together. Because the object is out on the heap, the only way to access it is through the pointer. To access the members through the pointer, you use a special notation — a minus sign followed by a greater-than sign. It bears a passing resemblance to an arrow (and is therefore called the arrow operator), as the following line makes clear:

MyPen->InkColor = red;

This goes through the MyPen pointer to set the InkColor property of the object to red.

As with other variables you created with new, after you are finished using an object, you should call delete to free the memory used by the object pointed to by MyPen. To do so, start with the word delete and then the name of the object pointer, MyPen, as in the following:

delete MyPen;

Remember Store a 0 in the pointer after you delete the object it points to. When you call delete on a pointer to an object, you are deleting the object itself, not the pointer. If you don’t store a 0 in the pointer, it still points to where the object used to be.

The PenClass3 example, shown in Listing 1-6, demonstrates the process of declaring a pointer, creating an object and pointing to it, accessing the object’s members through the pointer, deleting the object, and clearing the pointer back to 0.

LISTING 1-6: Managing an Object’s Life

#include <iostream>
#include "../PenClass2/Pen.h"

using namespace std;

int main() {
Pen *MyPen;
MyPen = new Pen;
MyPen->InkColor = red;
cout << MyPen->InkColor << endl;
delete MyPen;
MyPen = 0;
return 0;
}

Tip Table 1-2 reiterates the process (steps) shown in Listing 1-6 in a more formal way. The table is called “Steps to Using Objects” rather than something more specific such as “Using Objects with Pointers” because the majority of your work with objects will be through pointers. Therefore, this is the most common way of using pointers.

TABLE 1-2 Steps to Using Objects

Step

Sample Code

Action

1

Pen *MyPen;

Declares the pointer

2

MyPen = new Pen;

Calls new to create the object

3

MyPen->InkColor = red;

Accesses the members of the object through the pointer

4

delete MyPen;

Deletes the object

5

MyPen = 0;

Clears the pointer

Now that you have an overview of the process through Listing 1-6 and understand the basics through Table 1-2, you can see how to formalize the procedure. The following steps describe precisely how to work with raw pointers and objects:

  1. Declare the pointer.

    The pointer must match the type of object you intend to work with, except that the pointer’s type name in C++ is followed by an asterisk, *.

  2. Call new, passing the class name, and store the results of new in the pointer.

    You can combine Steps 1 and 2 into a single step.

  3. Access the object’s members through the pointer with the arrow operator, ->.

    You could dereference the pointer and put parentheses around it, but everyone uses the shorthand notation.

  4. When you are finished with the pointer, call delete.

    This step frees the object from the heap. Remember that this does not delete the pointer itself, but frees the object memory.

  5. Clear the pointer by setting it to 0.

    Tip If your delete statement is at the end of the application, you don’t need to clear the pointer to 0 because the pointer is going out of scope. The pointer won’t exist any longer, so setting it to 0 isn’t essential, but it’s good practice because you get into the habit of doing it in places where clearing the pointer to 0 would be important.

Using classes and smart pointers

If you’re working with C++ 17 or above, you probably want to use smart pointers with your objects, rather than the labor-intensive and error-prone raw pointers. The SmartPtr example, shown in Listing 1-7, shows the same process as Listing 1-6 but uses smart pointers instead. You still need to add Pen.cpp and Pen.h from PenClass2.

LISTING 1-7: Managing an Object’s Life Using Smart Pointers

#include <iostream>
#include <memory>
#include "../PenClass2/Pen.h"

using namespace std;

int main() {
unique_ptr<Pen> MyPen;
MyPen.reset(new Pen());
MyPen->InkColor = red;
cout << MyPen->InkColor << endl;
MyPen.reset();
return 0;
}

Remember You wouldn’t ordinarily assign an object to a unique_ptr as a separate step, but this example shows you how by using reset(). In this case, you actually reset MyPen to point to a new object, new Pen(), which must include the opening and closing parentheses. If you were to do this in an application, reset() would take care of freeing any old object before pointing MyPen to any new object. The “Creating smart pointers using std::unique_ptr and std::shared_ptr” section of Book 1, Chapter 8 shows the standard approach to creating smart pointers.

Notice that you still use the arrow operator to assign the color red to MyPen->InkColor and to retrieve the value later. This part of the code appears the same as when using a raw pointer. The final step is to free the object memory using reset(). The pointer will automatically delete itself, saving you a line of code in this example.

Passing objects to functions

When you write a function, normally you base your decision about using pointers on whether or not you want to change the original variables passed into the function. Suppose you have a function called AddOne(), and it takes an integer as a parameter. If you want to modify the original variable, you can use a pointer (or you can use a reference). If you don’t want to modify the variable, just pass the variable by value.

The following prototype represents a function that can modify the variable passed into it:

void AddOne(int *number);

And this prototype represents a function that cannot modify the variable passed into it:

void AddOne(int number);

With objects, you can do something similar. For example, this function takes a pointer to an object and can, therefore, modify the object:

void FixFlatTire(Car *mycar);

This version doesn’t allow modification of the original object:

void FixFlatTire(Car mycar);

However, unlike a primitive type, the function gets its own instance. In other words, every time you call this function, it creates an entirely new instance of class Car. This instance would be a duplicate copy of the myCar object that is an instance of class Car — it wouldn’t be the same instance.

When you work with objects, a complete copy is not always a sure thing. The original object may have properties that are pointers to other objects, but the object copy may not get copies of those pointers. The properties that contain pointers may end up blank (due to a lack of proper copying technique), point to the same values as the original (a shallow copy), or point to new variables (a deep copy). The difference is the kind of copy that the object provides:

  • Shallow: C++ copies the object and its property values precisely as provided in the original object. If the original object doesn’t rely on any sort of dynamic memory allocation, as is the case when working the primitives, the copy will work precisely as planned.
  • Deep: C++ not only copies the original object, but also allocates memory for any objects pointed to by the original object. So, the copy not only copies the original object, but any objects pointed to by that object. The two copies are completely separate.

Technical stuff A problem occurs when any of the subsidiary objects also have pointers to other objects. Now you have an entirely new level of objects to worry about. The topic of shallow and deep copying can become incredibly complex. If you want to know more, check out the article at https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/.

Tip The smart move with objects is to always pass objects as pointers. Don’t pass objects directly into functions. Yes, it risks bad code changing the object, but careful C++ programmers want the actual object, not a copy. Having access to the original outweighs the risk of an accidental change. This chapter explains how to prevent accidental changes by using the const parameters in the next section.

Because your function receives its objects as pointers, you continue accessing them by using the arrow operator. For example, the function FixFlatTire() may do this:

void FixFlatTire(Car *mycar) {
mycar->RemoveTire();
mycar->AddNewTire();
}

Or, if you prefer references, you would do this:

void FixFlatTire2(Car &mycar) {
mycar.RemoveTire();
mycar.AddNewTire();
}

Remember that pointers contain the address of an object, while a reference is simply another name (alias) for an object. Even though the reference is still an address, it’s the actual address of the object, rather than a pointer to the object. (Book 1, Chapter 8 discusses pointers in more detail.) In this code, because you’re dealing with a reference, you access the object’s members using the dot operator (.) rather than the arrow operator (->).

Tip Another reason to use only pointers and references as parameters for objects is that a function that takes an object as a parameter usually wants to change the object. Such changes require pointers or references.

Using const parameters in functions

A constant is a variable or object that another function can’t change even when you pass a reference to it to another function. To define a variable or an object as constant, unchangeable, you use the const keyword. For example, to define a variable as constant, you use:

const int MyInt = 3;

If someone were to come along and try to use this code:

MyInt = 4;

The compiler would display an error message saying, error: assignment of read-only variable ’MyInt’. The same holds true for a function using a const primitive like this one:

void DisplayInt(const int Value) {
cout << Value << endl;
}

It’s possible to display Value or interact with it in other ways, but trying to change Value will raise an error. This version will raise an error because Value is being changed:

void DisplayInt(const int Value) {
Value += 1;
cout << Value << endl;
}

The const keyword is useful when working with objects because you generally don’t want to pass an object directly. That involves copying the object, which is messy. Instead, you normally pass by using a pointer or reference, which would allow you to change the object. If you put the word const before the parameter, the compiler won’t allow you to change the parameter. The PenClass4 example that appears in Listing 1-8 has const inserted before the parameter. The function can look at the object but can’t change it.

LISTING 1-8: The Inspect Function Is Not Allowed to Modify Its Parameter

#include <iostream>
#include "../PenClass2/Pen.h"

using namespace std;

void Inspect(const Pen *Checkitout) {
cout << Checkitout->Brand << endl;
}

int main() {
Pen *MyPen = new Pen();
MyPen->Brand = "Spy Plus Camera";
Inspect(MyPen);
return 0;
}

Now suppose that you tried to change the object in the Inspect function. You may have put a line in that function like this:

Checkitout->Length = 10.0;

If you try this, the compiler issues an error. In Code::Blocks, you get: error: assignment of member ’Pen::Length’ in read-only object.

Remember If you have multiple parameters, you can mix const and non-const. If you go overboard, this can be confusing. The following line shows two parameters that are const and another that is not. The function can modify only the members of the object called one.

void Inspect(const Pen *Checkitout, Spy *one,
const Spy *two);

Using the this pointer

Consider a function called OneMoreCheeseGone(). It’s not a method, but it takes an object of instance Cheese as a parameter. Its prototype looks like this:

void OneMoreCheeseGone(Cheese *Block);

This is just a simple function with no return type. It takes an object pointer as a parameter. For example, after you eat a block of cheese, you can call:

OneMoreCheeseGone(MyBlock);

Now consider this: If you have an object on the heap, it has no name. You access it through a pointer variable that points to it. But what if the code is currently executing inside a method of an object? How do you refer to the object itself?

C++ has a secret variable that exists inside every method: this. It’s a pointer variable. The this variable always points to the current object. So if code execution is occurring inside a method and you want to call OneMoreCheeseGone(), passing in the current object (or block of cheese), you would pass this.

The following sections discuss what you might call the standard use of this, the version of this that exists in most code now. Once you understand the standard use of this, you move on to modifications to this that occur in C++ 20. Like most pointer usage in C++ 20, this has undergone changes to make it safer, smarter, and easier.

Defining standard this pointer usage

This section tells you how this is used for application development in most applications today. The CheeseClass example, shown in Listing 1-9, demonstrates this.

LISTING 1-9: Passing an Object from Inside Its Methods by Using the this Variable

#include <iostream>

using namespace std;

class Cheese {
public:
string status;
void eat();
void rot();
};

int CheeseCount;

void OneMoreCheeseGone(Cheese *Block) {
CheeseCount--;
Block->status = "Gone";
};

void Cheese::eat() {
cout << "Eaten up! Yummy" << endl;
OneMoreCheeseGone(this);
}

void Cheese::rot() {
cout << "Rotted away! Yuck" << endl;
OneMoreCheeseGone(this);
}

int main() {
Cheese *asiago = new Cheese();
Cheese *limburger = new Cheese();

CheeseCount = 2;
asiago->eat();
limburger->rot();

cout << endl;
cout << "Cheese count: " << CheeseCount << endl;
cout << "asiago: " << asiago->status << endl;
cout << "limburger: " << limburger->status << endl;
return 0;
}

The this listing has four main parts. First is the definition for the class called Cheese. The class contains a couple of methods.

Next is the function OneMoreCheeseGone() along with a global variable that it modifies. This function subtracts one from the global variable and stores a string in a property, status, of the object passed to it.

Next come the actual methods for class Cheese. (You must put these functions after OneMoreCheeseGone() because they call it. If you use a function prototype as a forward reference for OneMoreCheeseGone(), the order doesn’t matter.)

Finally, main() creates two new instances of Cheese. Then it sets the global variable to 2, which keeps track of the number of blocks left. Next, it calls the eat() function for the asiago cheese and rot() for the limburger cheese. And then it prints the results of everything that happened: It displays the Cheese count, and it displays the status of each object.

When you run the application in Listing 1-9, you see this output:

Eaten up! Yummy
Rotted away! Yuck

Cheese count: 0
asiago: Gone
limburger: Gone

The first line is the result of calling asiago->eat(), which prints one message. The second line is the result of calling limburger->rot(), which prints another message. The third line is simply the value in the variable CheeseCount. This variable was decremented once each time the computer called OneMoreCheeseGone(). Because the function was called twice, CheeseCount went from 2 to 1 to 0. The final two lines show the contents of the status variable in the two objects. (OneMoreCheeseGone() stores "Gone" in these variables.)

Take a careful look at the OneMoreCheeseGone() function. It operates on the current object provided as a parameter by setting its status variable to the string Gone. The eat() method calls it, passing the current object using this. The rot() method also calls it, again passing the current object via this.

Changes to the this pointer in C++ 20

Unless you’re actually working with C++ 20 at a somewhat detailed level, you can probably skip this section and not really lose much. Of course, you may just be curious and learning something new is always a good thing.

C++ 20 brings a few changes to the this pointer with it. Even though you don’t see anything about functional programming until Book 3, it’s important to know that like the examples in this chapter, you can use the this pointer in a lambda expression. A lambda expression is a mathematically based approach to dealing with certain kinds of programming problems that is concise and easier to understand than some standard C++ approaches. You can also pass a lambda expression, essentially a kind of function, to other functions as you would any other argument. The change of the use of the this pointer for lambda expressions is simply a clarification — you must now actually declare use of the this pointer before you’re allowed to use it. You can get an overview of lambda expressions in the “Using Lambda Expressions for Implementation” section of Book 3 Chapter 1 and read about using lambda expressions in your code in Book 3 Chapter 2. The discussion at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0806r2.html will fill in some very technical details if you’re interested.

It’s important to note that the C++ definition of an object as described in this chapter differs from the definition used by some other languages. There is lengthy and involved discussion of the topic at https://blog.panicsoftware.com/objects-their-lifetimes-and-pointers/, but the point is that if you understand objects as described in this chapter, then you know how C++ developers view them. You may have noticed that there is a great deal of emphasis in this chapter on destroying objects by releasing their storage. The “Starting and Ending with Constructors and Destructors” section of this chapter discusses another technique, which is to call a destructor. However, until C++ 20, standard objects, such as string, don’t have a destructor as such, the calling of it is a no-op (a no operation, nothing happens). Because the manner in which objects are destroyed is changing, so is the use of the this pointer, which relies on the existence of an object to work.

Technical stuff The this pointer can also come into play in situations that most people are unlikely to see unless they’re performing advanced tasks. For example, you can use the this pointer to access initialized members of a partially constructed object—one that hasn’t had every member fully initialized.

Overloading methods

You may want a method in a class to handle different types of parameters. For example, you might have a class called Door and a method called GoThrough(). You might want the GoThrough() method to take as parameters objects of class Dog, class Human, or class Cat. Depending on which class is entering, you might want to change the GoThrough() function’s behavior.

A way to handle this is by overloading the GoThrough() function. C++ lets you design a class that has multiple methods that are all named the same. However, the parameters must differ between these methods. With the GoThrough() method, one version will take a Human, another a Dog, and another a Cat.

View the code for the DoorClass example in Listing 1-10 and notice the GoThrough() methods. There are three of them. To use these methods, main() creates four different objects — a cat, a dog, a human, and a door. It then sends each creature through the door.

LISTING 1-10: Overloading Functions in a Class

#include <iostream>

using namespace std;

class Cat {
public:
string name;
};

class Dog {
public:
string name;
};

class Human {
public:
string name;
};

class Door {
private:
int HowManyInside;
public:
void Start();
void GoThrough(Cat *acat);
void GoThrough(Dog *adog);
void GoThrough(Human *ahuman);
};

void Door::Start() {
HowManyInside = 0;
}

void Door::GoThrough(Cat *somebody) {
cout << "Welcome, " << somebody->name << endl;
cout << "A cat just entered!" << endl;
HowManyInside++;
}

void Door::GoThrough(Dog *somebody) {
cout << "Welcome, " << somebody->name << endl;
cout << "A dog just entered!" << endl;
HowManyInside++;
}

void Door::GoThrough(Human *somebody) {
cout << "Welcome, " << somebody->name << endl;
cout << "A human just entered!" << endl;
HowManyInside++;
}

int main() {
Door entrance;
entrance.Start();

Cat *SneekyGirl = new Cat;
SneekyGirl->name = "Sneeky Girl";
Dog *LittleGeorge = new Dog;
LittleGeorge->name = "Little George";
Human *me = new Human;
me->name = "John";

entrance.GoThrough(SneekyGirl);
entrance.GoThrough(LittleGeorge);
entrance.GoThrough(me);

delete SneekyGirl;
delete LittleGeorge;
delete me;
return 0;
}

The application allows dogs and cats to enter like humans. The beginning of this application declares three classes, Cat, Dog, and Human, each with a name member. Next is the Door class. A private member, HowManyInside, tracks how many beings have entered. The Start() function activates the door. Finally, the class contains the overloaded functions. They all have the same name and the same return type. You can have different return types, but for the compiler to recognize the functions as unique, they must differ by parameters. These do; one takes a Cat pointer; one takes a Dog pointer; and one takes a Human pointer.

Next is the code for the methods. The first function, Start() sets HowManyInside to 0. The next three functions are overloaded. They do similar things, but they write slightly different messages. Each takes a different type.

The first step in main() is to create a Door instance. The code doesn’t use a pointer to show that you can mix pointers with stack variables in an application. After creating the Door instance, the code calls Start(). Next, the code creates three creature instances: Cat, Dog, and Human, and sets the name property for each.

The calls to the entrance.GoThrough() method passes a Cat, a Dog, and a Human (all in order). Because you can see the Door class, you know the code calls three different methods that are all named the same. But when using the class, you consider them one method that accepts a Cat, a Dog, or a Human. That’s the goal of overloading: to create what feels like versions of the one function. Here’s what you see when you run this application:

Welcome, Sneeky Girl
A cat just entered!
Welcome, Little George
A dog just entered!
Welcome, John
A human just entered!

Starting and Ending with Constructors and Destructors

You can add two special methods to your class that let you provide special startup and shutdown functionality: a constructor and a destructor. The following sections provide details about these methods.

Starting with constructors

When you create a new instance of a class, you may want to do some basic object setup. Suppose you have a class called Apartment, with a private property called NumberOfOccupants and a method called ComeOnIn(). The code for ComeOnIn() adds 1 to NumberOfOccupants.

When you create a new instance of Apartment, you probably want to start NumberOfOccupants at 0. The best way to do this is by adding a special method, a constructor, to your class. This method has a line of code such as

NumberOfOccupants = 0;

Whenever you create a new instance of the class Apartment, the computer first calls this constructor for your new object, thereby setting NumberOfOccupants to 0. Think of the constructor as an initialization function: The computer calls it when you create a new object.

To write a constructor, you add it as another method to your class, and make it public. You name the constructor the same as your class. For the class Apartment, you name the constructor Apartment(). The constructor has no return type, not even void. You can have parameters in a constructor; see “Adding parameters to constructors,” later in this chapter. Listing 1-11, later in this section, shows a sample constructor along with a destructor, which is covered in the next section.

Ending with destructors

When you delete an instance of a class, you might want some cleanup code to straighten things out before the object memory is released. For example, your object may have properties that are pointers to other objects. It’s essential to delete those other objects. You put cleanup code in a special function called a destructor. A destructor is a finalization function that the computer calls before it deletes your object.

The destructor function gets the same name as the class, except it has a tilde, ~, at the beginning of it. (The tilde is usually in the upper-left corner of the keyboard.) For a class called Squirrel, the destructor would be ~Squirrel(). The destructor doesn’t have a return type, not even void, because you can’t return anything from a destructor (the object is gone, after all). You just start with the function name and no parameters. The next section, “Sampling constructors and destructors,” shows an example that uses both constructors and destructors.

Tip Constructors and destructors are a way of life for C++ programmers. Nearly every class has a constructor, and many also have a destructor.

Sampling constructors and destructors

The WalnutClass example, shown in Listing 1-11, uses a constructor and destructor. This application involves two classes, the main one called Squirrel that demonstrates the constructor and destructor, and one called Walnut, which is used by the Squirrel class.

LISTING 1-11: Initializing and Finalizing with Constructors and Destructors

#include <iostream>

using namespace std;

class Walnut {
public:
int Size;
};

class Squirrel {
private:
Walnut *MyDinner;
public:
Squirrel();
~Squirrel();
};

Squirrel::Squirrel() {
cout << "Starting!" << endl;
MyDinner = new Walnut;
MyDinner->Size = 30;
}

Squirrel::~Squirrel() {
cout << "Cleaning up my mess!" << endl;
delete MyDinner;
}

int main() {
Squirrel *Sam = new Squirrel;
Squirrel *Sally = new Squirrel;

delete Sam;
delete Sally;
return 0;
}

The Squirrel class has a property called MyDinner that is a pointer to a Walnut instance. The Squirrel constructor creates an instance of Walnut and stores it in MyDinner. The destructor deletes the instance of Walnut. In main(), the code creates two instances of Squirrel. Each instance gets its own Walnut to eat. Each Squirrel creates its Walnut when it starts and deletes the Walnut when the Squirrel is deleted.

Notice in this code that the constructor has the same name as the class, Squirrel(). The destructor also has the same name, but with a tilde, ~, tacked on to the beginning of it. Thus, the constructor is Squirrel() and the destructor is ~Squirrel(). Destructors never take parameters and you can’t call them directly, but the runtime calls them automatically when it’s time to destroy an object.

When you run this application, you can see the following lines, which were spit up by the Squirrel in its constructor and destructor. (You see two lines of each because main() creates two squirrels.)

Starting!
Starting!
Cleaning up my mess!
Cleaning up my mess!

If the Walnut class also had a constructor and destructor, and you made the MyDinner property a variable in the Squirrel class, rather than a pointer, the computer would create the Walnut instance after it creates the Squirrel instance, but before it calls the Squirrel() constructor. It then deletes the Walnut instance when it deletes the Squirrel instance, after calling the ~Squirrel() destructor. The code performs these steps for each instance of Squirrel.

Adding parameters to constructors

Like other methods, constructors allow you to include parameters. When you do, you can use these parameters in the initialization process. To use them, you list the arguments inside parentheses when you create the object. Because constructors have parameters, you can create multiple overloaded constructors for a class by varying the number and type of parameters.

Technical stuff Although int has a constructor, it isn’t a class. However, the runtime library (that big mass of code that gets put in with your application by the linker) includes a constructor and destructor that you can use when calling new for an integer.

Suppose that you want the Squirrel class to have a name property. Although you could create an instance of Squirrel and then set its name property, you can specify the name directly by using a constructor. The constructor’s prototype looks like this:

Squirrel(string StartName);

Then, you create a new instance like so:

Squirrel *Sam = new Squirrel("Sam");

The constructor is expecting a string, so you pass a string when you create the object.

The SquirrelClass example, shown in Listing 1-12, presents an application that includes all the basic elements of a class with a constructor that accepts parameters.

LISTING 1-12: Placing Parameters in Constructors

#include <iostream>

using namespace std;

class Squirrel {
private:
string Name;
public:
Squirrel(string StartName);
void WhatIsMyName();
};

Squirrel::Squirrel(string StartName) {
cout << "Starting!" << endl;
Name = StartName;
}

void Squirrel::WhatIsMyName() {
cout << "My name is " << Name << endl;
}

int main()
{
Squirrel *Sam = new Squirrel("Sam");
Squirrel *Sally = new Squirrel("Sally");

Sam->WhatIsMyName();
Sally->WhatIsMyName();

delete Sam;
delete Sally;
return 0;
}

In main(), you pass a string into the constructors. The constructor code takes the StartName parameter and copies it to the Name property. The WhatIsMyName() method writes Name to the console.

Building Hierarchies of Classes

When you start going crazy describing classes, you usually discover hierarchies of classes. For example, you have a class Vehicle that you want to divide into classes: Car, PickupTruck, TractorTrailer, and SUV. The Car class is further divided into the StationWagon, FourDoorSedan, and TwoDoorHatchback classes.

Or you could divide Vehicle into car brands, such as Ford, Honda, and Toyota. Then you could divide the class Toyota into models, such as Prius, Avalon, Camry, and Corolla. You can create similar groupings of objects for the other class hierarchies; your decision depends on how you categorize things and how the hierarchy is used. In the hierarchy, class Vehicle is at the top. This class has properties you find in every brand or model of vehicle. For example, all vehicles have wheels. How many they have varies, but it doesn’t matter at this point, because classes don’t have specific values for the properties.

Each brand has certain characteristics that might be unique to it, but each has all the characteristics of class Vehicle. That’s called inheritance. The class Toyota, for example, has all the properties found in Vehicle. And the class Prius has all the properties found in Toyota, which includes those inherited from Vehicle.

Creating a hierarchy in C++

In C++, you can create a hierarchy of classes. When you take one class and create a new one under it, such as creating Toyota from Vehicle, you are deriving a new class, which means Toyota is a child of Vehicle in the hierarchy.

To derive a class from an existing class, you write the new class as you would any other class, but you extend the header after the class name with a colon, :, the word public, and then the class you’re deriving from, as in the following class header line:

class Toyota : public Vehicle {

When you do so, the class you create (Toyota) inherits the properties and methods from the parent class (Vehicle). For example, if Vehicle has a public property called NumberOfWheels and a public method called Drive(), the class Toyota has these members, although you didn’t write the members in Toyota.

The VehicleClass example, shown in Listing 1-13, demonstrates class inheritance. It starts with a class called Vehicle, and a derived class called Toyota. You create an instance of Toyota in main() and call two methods for the instance, MeAndMyToyota() and Drive(). The definition of the Toyota class doesn’t show a Drive() function. The Drive() function is inherited from the Vehicle class. You can call this function like a member of the Toyota class because in many ways it is.

LISTING 1-13: Deriving One Class from Another

#include <iostream>

using namespace std;

class Vehicle {
public:
int NumberOfWheels;

void Drive() {
cout << "Driving, driving, driving…" << endl;
}
};

class Toyota : public Vehicle {
public:
void MeAndMyToyota() {
cout << "Just me and my Toyota!" << endl;
}
};

int main() {
Toyota MyCar;
MyCar.MeAndMyToyota();
MyCar.Drive();
return 0;
}

When you run this application, you see the output from two functions:

Just me and my Toyota!
Driving, driving, driving…

Understanding types of inheritance

When you create a class, its methods can access both public and private properties and methods. Users of the class can access only the public properties and methods. When you derive a new class, it cannot access the private members in the parent class. Private members are reserved for a class itself and not for any derived class. When members need to be accessible by derived classes, there’s a specification you can use beyond public and private: protected.

Remember Protected members and private members work the same way from a user perspective, but derived classes can access both protected and public members. Private members are hidden from both users and derived classes. Always use protected members when possible when you plan to derive classes from a parent class.

Creating and Using Object Aliases

An alias is another name for something. If your name is Robert, someone could use an alias of Bob when calling your name. Both Robert and Bob point to the same person — you. However, the names are actually different. One is your real name, Robert, and the other is your alias, Bob. In real life, using aliases can make things easier: saying Bob is definitely easier than saying Robert (although not by much). Using aliases in C++ applications can make things easier, too.

Remember One of the most common reasons to use an alias in C++ is to change the manner in which an object is accessed. Moving a pointer to an object is always going to be easier than moving the object itself because a pointer is simply a number that specifies the address of the object. The object could contain complex data and pointers to yet other objects. Moving objects is complicated and messy, so developers try to avoid it at all cost.

However, sending a pointer to someone gives the recipient access to the original data. The recipient could modify the data in ways that you don’t want. So, you could create an alias of the original object that is a constant. No one can modify a constant. The ObjectAlias example, shown in Listing 1-14, demonstrates how to create a constant alias of a string object. The same technique works with any other sort of object you might want to work with.

LISTING 1-14: Creating an Object Alias

#include <iostream>

using namespace std;

int main() {
string OriginalString = "Hello";
const string &StringCopy(OriginalString);
OriginalString = "Goodbye";
cout << OriginalString << endl;
cout << StringCopy << endl;
return 0;
}

The code begins by creating a string named OriginalString that contains a value of Hello. It then creates a const string alias of OriginalString named StringCopy. When the code changes the value of OriginalString, the value of StringCopy is also changed because StringCopy points to the same location in memory. So when you run this example, you see output of

Goodbye
Goodbye

It may not seem like you’ve accomplished anything, but if you try to modify the value of StringCopy, Code::Blocks outputs an error message like this:

error: passing 'const string {aka const
std::basic_string<char>}' as 'this' argument of
'std::basic_string<_CharT, _Traits, _Alloc>&
std::basic_string<_CharT, _Traits,
_Alloc>::operator=(const _CharT*) [with _CharT = char;
_Traits = std::char_traits<char>; _Alloc =
std::allocator<char>; std::basic_string<_CharT, _Traits,
_Alloc> = std::basic_string<char>]' discards qualifiers
[-fpermissive]|

The point is that you can’t modify the value of StringCopy, but you can modify the value of OriginalString. Sending StringCopy to someone who needs access to the value is safe. Just to ensure that you understand what is happening, try making StringCopy a standard string rather than a const string. You’ll be able to modify the value, and the modification will now affect OriginalString as well. StringCopy truly is an alias of OriginalString, but as a const string, it’s an alias that prevents modification of the underlying string value.