Chapter 4
IN THIS CHAPTER
Using polymorphism effectively
Adjusting member access when deriving new classes
Multiple-inheriting new classes
Making virtual inheritance work correctly
Putting one class or type inside another
Classes are amazingly powerful. You can do so much with them. In this chapter, you discover many of the extra features you can use in your classes. But these aren’t just little extras that you may want to use on occasion. If you follow the instructions in this chapter, you should find that your understanding of classes in C++ greatly improves, and you’ll want to use many of these topics throughout your programming.
This chapter also discusses many of the issues that come up when you’re deriving new classes and inheriting members. This discussion includes virtual inheritance and multiple inheritance, topics that people mess up a lot. As part of this discussion, you see the ways you can put classes and types inside other classes.
Without inheritance, doing object-oriented programming (OOP) would be nearly impossible. Yes, you could divide your work into objects, but the real power comes from inheritance. However, you have to be careful when using inheritance or you can really cause yourself problems. In the sections that follow, you see different ways to use inheritance — and how to keep it all straight.
Polymorphism refers to using an object as an instance of a base class. For example, if you have the class Creature
and from that you derive the class Platypus
, you can treat the instances of class Platypus
as if they’re instances of class Creature
. This concept is useful if you have a function that takes as a parameter a pointer to Creature
. You can pass a pointer to Platypus
.
However, you can’t go further than that. You can’t take a pointer to a pointer to Creature
. (Remember that when creating a pointer to a pointer, the first pointer is the address of the second pointer variable.) So if you have a function such as this:
void Feed1(Creature *c) {
cout << "Feed me!" << endl;
}
you’re free to pass the address of a Platypus
object, as in the following:
Platypus *plato = new Platypus;
Feed1(plato);
However, with a function that takes the address of a pointer variable (note the two asterisks in the parameter), like this:
void Feed2(Creature **c) {
cout << "Feed me!" << endl;
}
you can’t pass the address of a pointer to a Platypus
instance, as in this example:
Platypus *plato = new Platypus;
Feed2(&plato);
If you try to compile this code, you get a compiler error.
You don’t always use polymorphism when you declare a variable, as shown in the previous section. If you do, you’re declaring variables like this:
Creature *plato = new Platypus;
The type of plato
is a pointer to Creature
. But the object is a Platypus
. You can create a Platypus
from a Creature
because a pointer to a base class can point to an object of a derived class. But now the compiler thinks that plato
is a pointer to a Creature
instance, so you can’t use plato
to call a Platypus
method — you can use plato
only to call Creature
methods. For example, if your two classes look like this:
class Creature {
public:
void EatFood() {
cout << "I'm eating!" << endl;
}
};
class Platypus : public Creature {
public:
void SingLikeABird() {
cout << "I'm siiiiiinging in the rain!" << endl;
}
};
the following code doesn’t work:
Creature *plato = new Platypus;
plato->SingLikeABird();
Although the first line compiles, the second doesn’t. When the compiler gets to the second line, it thinks that plato
is an object of class type Creature
, and Creature
doesn’t have a method called SingLikeABird()
, so the compiler gets upset. You can fix the situation by casting, like this:
Creature *plato = new Platypus;
static_cast <Platypus *>(plato)->SingLikeABird();
If you want to save some work, start by declaring plato
as type Platypus
, as shown here:
Platypus *plato = new Platypus;
plato->SingLikeABird();
You may need to perform a cast at times. For example, you may have a variable that can hold an instance of an object or its derived object. Then you have to use polymorphism, as in the following code:
Creature *plato;
if (HasABeak == true) {
plato = new Platypus;
} else {
plato = new Creature;
}
This code defines a pointer to Creature
. That pointer stores the address of either a Platypus
instance or a Creature
instance, depending on what’s in the HasABeak
variable.
But if you use an if
statement like that, you shouldn’t follow it with a call to SingLikeABird()
, even if you cast it:
static_cast <Platypus *>(plato)->SingLikeABird();
The reason is that if the else
clause took place and plato
holds an instance of Creature
, not Platypus
, the plato
object won’t have a SingLikeABird()
method. Either you get some type of error message when you run the application or you don’t, but the application will mess up later. And those messing-up-later errors are the worst kind to try to fix.
You may have a class that has protected members; and in a derived class, you may want to make these members public. You transition the members to public by adjusting the access. You have two ways to do this: One is the older way, and the other is the newer American National Standards Institute (ANSI) way, which is the method supported by the current version of the GNU Compiler Collection (GCC). If your compiler supports the newer way, the creators of the ANSI standard ask that you use the ANSI way.
In the following classes, Secret
has a member, X
, that is protected. The derived class, Revealed
, makes the member X
public. Here’s the older way:
class Secret {
protected:
int X;
};
class Revealed : public Secret {
public:
Secret::X;
};
The code declares the member X
public by providing the base class name, two colons, and then the member name. It didn’t include any type information; that was implied. So in the class Secret
, the member X
is protected. But in Revealed
, it’s public.
Here’s the ANSI way, which requires the word using
. Otherwise, it’s the same:
class Secret {
protected:
int X;
};
class Revealed : public Secret {
public:
using Secret::X;
};
Now, when you use the Revealed
class, the inherited member X
is public, but X
is still protected in the base class, Secret
.
If you want to make a protected member public in a derived class, don’t just redeclare the member. If you do, you end up with two properties of the same name within the class; and needless to say, that can be confusing! Look at the following two classes:
class Secret {
protected:
int X;
public:
void SetX() {
X = 10;
}
void GetX() {
cout << "Secret X is " << X << endl;
}
};
class Revealed : public Secret {
public:
int X;
};
The Revealed
class has two int X
members! Suppose you try this code with it:
Revealed me;
me.SetX();
me.X = 30;
me.GetX();
The first line declares the variable. The second line calls SetX()
, which stores 10
in the inherited X
, because SetX()
is part of the base class. The third line stores 30
in the new X
declared in the derived class. GetX()
is part of the base class, so it prints 10
.
Having two properties of the same name is confusing. It would be best if the compiler didn’t allow you to have two variables of the same name. But just because the compiler allows it doesn’t mean you should do it. Having two variables of the same name is a perfect way to increase the chances of bugs creeping into your application.
Suppose you have a class that has several public members, and when you derive a new class, you want all the public members to become protected, except for one. You can do this task in a couple of ways. You can adjust the access of all the members except for the one you want left public. Or, if you have lots of members, you can take the opposite approach. Look at this code:
class Secret {
public:
int Code, Number, SkeletonKey, System, Magic;
};
class AddedSecurity : protected Secret {
public:
using Secret::Magic;
};
The derived class inherits the base class as protected
, as you can see in the header line for AddedSecurity
. That means that all the inherited public members of Secret
are protected in the derived class. But then the code promotes Magic
back to public
by adjusting its member access. Thus, Magic
is the only public member of AddedSecurity
. All the rest are protected.
Two words that sound similar and have similar meanings in computer programming are overload and override. To overload means to take a function and write another function of the same name that takes a different set of parameters. To override means to take an existing function in a base class and give the function new code in a derived class. The function in the derived class has the same prototype as the base class: It takes the same parameters and returns the same type.
An overloaded function can optionally return a different type, but the parameters must be different from the original function, whether in number or type or both. The overloaded function can live in the same class or in a derived class. The idea here is to create what appears to be a single function that can take several types of parameters. For example, you may have a function called Append()
that works on strings. By using Append()
, you’d be able to append a string to the end of the string represented by the instance, or you could append a single character to the end of the string represented by the instance. Now, although it feels like one function called Append()
, really you would implement it as two separate functions: one that takes a string parameter and one that takes a character parameter.
This section discusses one particular issue dealing with overriding functions (that is, replacing a function in a derived class). Generally, the overriding function must have the same parameter types and must return the same type as the original function. A situation exists under which you can violate this rule, although only slightly. You can violate the rule of an overriding function returning the same type as the original function if all three of the following are true:
LISTING 4-1: Overriding and Returning a Derived Class
#include <iostream>
#include <map>
using namespace std;
class Peripheral {
public:
string Name;
int Price;
int SerialNumber;
Peripheral(string aname, int aprice, int aserial) :
Name(aname), Price(aprice),
SerialNumber(aserial) {}
};
class Printer : public Peripheral {
public:
enum PrinterType {laser, inkjet};
PrinterType Type;
Printer(string aname, PrinterType atype, int aprice,
int aserial) :
Peripheral(aname, aprice, aserial), Type(atype) {}
};
typedef map<string, Peripheral *> PeripheralMap;
class PeripheralList {
public:
PeripheralMap list;
virtual Peripheral *GetPeripheralByName(string name);
void AddPeripheral(string name, Peripheral *per);
};
class PrinterList : public PeripheralList {
public:
Printer *GetPeripheralByName(string name);
};
Peripheral *PeripheralList::GetPeripheralByName
(string name){
return list[name];
}
void PeripheralList::AddPeripheral(
string name, Peripheral *per) {
list[name] = per;
}
Printer *PrinterList::GetPeripheralByName(string name) {
return static_cast<Printer *>(
PeripheralList::GetPeripheralByName(name));
}
int main(int argc, char *argv[]) {
PrinterList list;
list.AddPeripheral(string("Koala"),
new Printer("Koala", Printer::laser,
150, 105483932)
);
list.AddPeripheral(string("Bear"),
new Printer("Bear", Printer::inkjet,
80, 5427892)
);
Printer *myprinter = list.GetPeripheralByName("Bear");
if (myprinter != 0) {
cout << myprinter->Price << endl;
}
return 0;
}
This example uses a special type called map
, which is simply a container or list that holds items in pairs. The first item in the pair is called a key, and the second item is called a value. You can retrieve values from the map
based on the key. This example stores a Peripheral
(the value) based on a name, which is a string (the key). The example uses a typedef
to create the map
by specifying the two types involved: first the key and then the value. The typedef
, then, looks like this:
typedef map<string, Peripheral *> PeripheralMap;
This line creates a type of a map
that stores a set of Peripheral
instances and you can look them up based on a name. The code uses a notation similar to that of an array to put an item in the map
, where list
is the map
, name
is a string
, and per
is a pointer to Peripheral
. The key goes inside square brackets, like this:
list[name] = per;
To retrieve the item, you refer to the map
entry using brackets again, as in this line from the listing:
return list[name];
Listing 4-1 shows a Printer
class derived from a Peripheral
class. It also has a container
class called PrinterList
derived from PeripheralList
. The idea is that the PrinterList
holds only instances of the class called Printer
. So the code overrides the GetPeripheralByName()
function. The version inside PrinterList
casts the item to a Printer
because the items in the list are instances of Peripheral
. If you were to leave this function as is, every time you want to retrieve a Printer
, you’d get back a pointer to a Peripheral
instead, and you’d have to cast it to a (Printer *)
type. Overriding the GetPeripheralByName()
function and performing the cast there is easier and more efficient.
In C++, having a single base class from which your class inherits is generally best. However, it is possible to inherit from multiple base classes, a process called multiple inheritance.
One class may have some features that you want in a derived class, and another class may have other features that you want in the same derived class. If that’s the case, you can inherit from both through multiple inheritance.
LISTING 4-2: Deriving from Two Different Classes
#include <iostream>
using namespace std;
class Mom {
public:
void Brains() {
cout << "I'm smart!" << endl;
}
};
class Dad {
public:
void Beauty() {
cout << "I'm beautiful!" << endl;
}
};
class Derived : public Mom, public Dad {
};
int main(int argc, char *argv[]) {
Derived child;
child.Brains();
child.Beauty();
return 0;
}
When you run this code, you see the following output:
I'm smart!
I'm beautiful!
In the preceding code, the class Derived
inherited the functions of both classes Mom
and Dad
. Because it did, the compiler allows a Derived
instance, child
, to call both functions. You use this approach to derive from multiple classes:
class Derived : public Mom, public Dad
You start with the base classes to the right of the single colon, as with a single inheritance, and separate the classes with a comma. You also precede each class with the type of inheritance, public
.
As with single inheritance, you can use inheritance other than public
. But you don’t have to use the same access for all the classes. For example, the following, although a bit confusing, is acceptable:
class Derived : public Mom, protected Dad
This means that public members derived from Dad
are now protected in the Derived
class, which also means that users can’t call the methods inherited from Dad
, nor can they access any properties inherited from Dad
. If you used this type of inheritance in Listing 4-2, this line would no longer work:
child.Beauty();
If you try to compile it, you see the following error, because the Beauty()
member is protected now:
'void Dad::Beauty()' is inaccessible
Strange, bizarre, freaky things can happen with multiple inheritance. If both base classes have a property called Bagel
, the compiler gets confused. Suppose you enhance the two base classes with a Bagel
effect (as seen in the DerivingTwoDiff2
example):
class Mom {
public:
int Bagel;
void Brains() {
cout << "I'm smart!" << endl;
}
};
class Dad {
public:
int Bagel;
void Beauty() {
cout << "I'm beautiful!" << endl;
}
};
class Derived : public Mom, public Dad {
};
In the preceding code, each of the two base classes, Mom
and Dad
, has a Bagel
member. The compiler will let you do this. But if you try to access the member, as in the following code, you get an error:
Derived child;
child.Bagel = 42;
Here’s the error message we see in Code::Blocks:
error: request for member 'Bagel' is ambiguous
The message means that the compiler isn’t sure which Bagel
the code refers to: The one inherited from Mom
or the one inherited from Dad
. If you write code like this, make sure you know which inherited member you’re referring to so you can fix the problem.
child.Mom::Bagel = 42;
Yes, that really is correct, even though it seems a little strange. And if you want to refer to the one by Dad
, you do this:
child.Dad::Bagel = 17;
Both lines compile properly because you removed any ambiguities. In addition, you can access them individually by using the same technique:
cout << child.Mom::Bagel << endl;
cout << child.Dad::Bagel << endl;
At times, you may see the word virtual
thrown in when deriving a new class, as in the following:
class Diamond : virtual public Rock
This inclusion of virtual
is to fix a strange problem that can arise. When you use multiple inheritance, you can run into a crazy situation in which you have a diamond-shaped inheritance, as in Figure 4-1.
In Figure 4-1, you can see that the base class is Rock
. The Diamond
and Jade
classes derive from Rock
. At this point, the code uses multiple inheritance to derive the class MeltedMess
from Diamond
and Jade
.
Yes, you can do this. But you have to be careful.
FIGURE 4-1: Using diamond inheritance can be hard.
Think about this: Suppose Rock
has a public member called Weight
. Then both Diamond
and Jade
inherit that member. Now when you derive MeltedMess
and try to access its Weight
member, the compiler claims that it doesn’t know which Weight
you’re referring to — the one inherited from Diamond
or the one inherited from Jade
. You know that there should only be one instance of Weight
, because it came from a single base class, Rock
. But the compiler sees only one level up, not two.
To understand how to fix the problem, recognize what happens when you create an instance of a class derived from another class: Deep down inside the computer, the instance has a portion that is itself an instance of the base class. When you derive a class from multiple base classes, instances of the derived class have one portion for each base class. Thus an instance of MeltedMess
has a portion that is a Diamond
and a portion that is a Jade
, as well as a portion that wasn’t directly inherited from Rock
.
Digging deeper, MeltedMess
has both a Diamond
in it and a Jade
in it, and each of those in turn has a Rock
in them, which means that the compiler sees two Rock
s in MeltedMess
. With each Rock
comes a separate Weight
instance. The CrackingDiamonds
example, shown in Listing 4-3, demonstrates the problem. This listing declares the classes Rock
, Diamond
, Jade
, and MeltedMess
.
LISTING 4-3: Cracking Diamonds
#include <iostream>
using namespace std;
class Rock {
public:
int Weight;
};
class Diamond : public Rock {
public:
void SetDiamondWeight(int newweight) {
Weight = newweight;
}
int GetDiamondWeight() {
return Weight;
}
};
class Jade : public Rock {
public:
void SetJadeWeight(int newweight) {
Weight = newweight;
}
int GetJadeWeight() {
return Weight;
}
};
class MeltedMess : public Diamond, public Jade {
};
int main(int argc, char *argv[])
{
MeltedMess mymess;
mymess.SetDiamondWeight(10);
mymess.SetJadeWeight(20);
cout << mymess.GetDiamondWeight() << endl;
cout << mymess.GetJadeWeight() << endl;
return 0;
}
One member is called Weight
, and it’s part of Rock
. The Jade
and Diamond
classes include two accessor methods, one to set the value of Weight
and one to get it.
The MeltedMess
class derives from both Diamond
and Jade
. The code creates an instance of MeltedMess
and calls the four methods that access the supposedly single Weight
member in Rock
. The code calls the accessor for Diamond
, setting Weight
to 10
. Then it calls the one for Jade
, setting Weight
to 20
.
In a perfect world, in which each object only has one Weight
, this would have first set the Weight
to 10
and then to 20
. When you print it, you should see 20
both times. But you don’t:
10
20
When you print the Diamond
portion of the MeltedMess
instance Weight
in Listing 4-3, shown previously, you see 10
. The Jade
portion displays 20
instead. Therefore, mymess
has two different Weight
members. That’s not a good thing.
To fix it, add the word virtual
when you inherit from Rock
. According to the ANSI standard, you put virtual
in the two middle classes (as shown in the CrackingDiamonds2
example provided with the downloadable source and explained in this section). This means Diamond
and Jade
in this case. Thus, you need to modify the class headers in Listing 4-3 to look like this:
class Diamond : virtual public Rock {
and this:
class Jade : virtual public Rock {
When you make these modifications and then run the application, you find that you have only one instance of Weight
in the final MeltedMess
class instance, mymess
. It’s not such a mess after all! Here’s the output after making the change:
20
20
Now this makes sense: Only one instance of Weight
is in the mymess
object, so the following line changes the Weight
to 10
:
mymess.SetDiamondWeight(10);
Then the following line changes the same Weight
to 20
:
mymess.SetJadeWeight(20);
You can also access Weight
directly now without error, so the accessor methods aren’t strictly needed:
mymess.Weight = 30;
Then the following lines print the value of the one Weight
instance, 30
:
cout << mymess.GetDiamondWeight() << endl;
cout << mymess.GetJadeWeight() << endl;
cout << mymess.Weight << endl;
You may encounter a situation in which you want one class to access the private and protected members of another class. Normally, doing so isn’t allowed. But it is if you make the two classes friends. C++ provides the friend
keyword to override the normal class protections.
Use friend
only when you really need to. If you have a class, say Square
, that needs access to the private and protected members of a class called DrawingBoard
, you can add a line inside the class DrawingBoard
that looks like this:
friend class Square;
This code allows the code in Square
to access the private and protected members of any instance of type DrawingBoard
.
LISTING 4-4: Working with Friends
#include <iostream>
using namespace std;
class PAndP;
class Limited {
public:
void ShowProtected(PAndP &);
};
class PAndP {
public:
friend class Peeks;
friend void Limited::ShowProtected(PAndP &X);
friend void FriendFunction(PAndP &X);
protected:
void IsProtected() {cout << "Protected" << endl;}
private:
string var = "Var";
void IsPrivate() {cout << "Private " << var << endl;}
};
class Peeks {
public:
void ShowProtected(PAndP &X) {X.IsProtected();}
void ShowPrivate(PAndP &X) {
X.var = "From Peeks";
X.IsPrivate();
}
};
void Limited::ShowProtected(PAndP &X){
X.IsProtected();
}
void FriendFunction(PAndP &X) {
X.IsProtected();
X.var = "From FriendFunction";
X.IsPrivate();
}
int main() {
PAndP Hidden;
Peeks ShowMe;
Limited ShowMeAgain;
ShowMe.ShowProtected(Hidden);
ShowMe.ShowPrivate(Hidden);
ShowMeAgain.ShowProtected(Hidden);
FriendFunction(Hidden);
return 0;
}
The private and protected (PAndP
) class contains protected and private members that Peeks
, Limited::ShowProtected()
, and FriendFunction()
access. Each of these entities takes a different route, and that route isn’t always as obvious as it might be.
When working with the Limited
class, coding order is important. You begin by creating a forward reference to class PAndP
and then define the Limited
class. However, you can’t define Limited::ShowProtected()
yet because the members of PAndP
aren’t known to the compiler and there isn’t a way to create a forward declaration of them. Consequently, the actual code for Limited::ShowProtected()
comes later, which is the only method in Limited
that has access to PAndP
. If you were to try accessing PAndP
from any other member, the compiler would complain.
Protected
Private From Peeks
Protected
Protected
Private From FriendFunction
Sometimes an application needs a fairly complex internal structure to get its work done. Three ways to accomplish this goal with relatively few headaches are nesting classes, embedding classes, and declaring types within classes. The following sections discuss the two most common goals: nesting classes and declaring types within classes. The “Nesting a class” section also discusses protection for embedded classes.
You may have times when you create a set of classes with one class acting as the primary class while all the other classes function as supporting classes. For example, you may be a member of a team of programmers, and your job is to write a set of classes that log on to a competitor’s computer at night and lower all the prices on the products. Other members of your team will use your classes in their applications. You’re just writing a set of classes; the teammates are writing the rest of the application. The following sections consider the issues involved in performing this task, as well as a potential solution in the form of nested classes.
In the classes you’re creating, you want to make the task easy on your coworkers. In doing so, you may make a primary class, such as EthicalCompetition
, that they will instantiate to use your set of classes. This primary class will include the methods for using the system. In other words, it serves as an interface to the set of classes.
In addition to the main EthicalCompetition
class, you might create additional auxiliary classes that the EthicalCompetition
class will use, but your coworkers won’t interact with directly. One might be a class called Connection
that handles the tasks of connecting to the competitor’s computer. However, the Connection
class may present these problems:
Connection
may be something you write, but your organization may support another Connection
class, and your coworkers might need to use that class.Connection
class. Perhaps it has special functionality that would reveal too many organizational secrets, or you just want them using the main interface, the EthicalCompetition
class.To solve the unique name problem, you have several choices. For one, you can just rename the class something different, such as EthicalCompetitionConnection
. But that’s a bit long for a class used exclusively for internal needs. However, you could shorten the class name and call it something that’s likely to be unique, such as ECConnection
.
Yet at the same time, if the users of your classes look at the header file and see a whole set of classes, which classes they should be using may not be clear. (Of course, you would write some documentation to clear this up, but you do want the code to be at least somewhat self-explanatory.)
One solution for dealing with both naming conflict and privacy issues for support classes is to use nested classes. With a nested class, you write the declaration for the main class, EthicalCompetition
, and then, inside the class, you write the supporting classes, as in the following:
class EthicalCompetition {
private:
class Connection {
public:
void Connect();
};
public:
void HardWork();
};
Note that this shows a class inside a class. Here’s the code for the functions:
void EthicalCompetition::HardWork() {
Connection c;
c.Connect();
cout << "Connected" << endl;
}
void EthicalCompetition::Connection::Connect() {
cout << "Connecting…" << endl;
}
The header for the Connect
function in the Connection
class requires first the outer class name, then two colons, then the inner class name, then two colons again, and finally the function name. This follows the pattern you normally use where you put the class name first, then two colons, and then the function name. But in this case, you have two class names separated with two colons.
When you want to declare an instance of the Connection
class, you do it differently, depending on where you are in the code when you declare it:
EthicalCompetition
class: You simply refer to the class by its name, Connection
. Look at the method HardWork
, with this line:
Connection c;
Connection
, without an instance of the outer class, EthicalCompetition
. To do this, you fully qualify the class name, like this:
EthicalCompetition::Connection myconnect;
This line would go, for instance, in the main()
function of your application if you want to create an instance of the inner class, Connection
.
However, you may recall that one of the reasons for putting the class inside the other was to shield it from the outside world, to keep your nosy coworkers from creating an instance of it. But so far, what you’ve done doesn’t really stop them from using the class. They can just use it by referring to its fully qualified name, EthicalCompetition::Connection
.
So far, you’ve created a handy grouping of the class, and you also set up your grouping so that you can use a simpler name that won’t conflict with other classes. If you just want to group your classes, you can use a nested class. If you want to add higher security to a class so that others can’t use your inner class, however, you have to create an inner class definition.
Here’s a series of three tricks devoted to showing you how you create that inner class definition. For the first trick, you declare the class with a forward definition but put the class definition outside the outer class. Never put the inner class definition inside a private or protected section of the outer class definition; it doesn’t work. The following code takes care of that declaration for you:
class EthicalCompetition {
private:
class Connection;
public:
void HardWork();
};
class EthicalCompetition::Connection {
public:
void Connect();
};
Here, inside the outer class, is a header for the inner class and a semicolon that you use instead of writing the whole inner class; that’s a forward declaration. The rest of the inner class appears after the outer class. To make this code work, you must fully qualify the class name, like this:
class EthicalCompetition::Connection
error: aggregate 'EthicalCompetition::Connection c' has incomplete type and cannot be defined
Remember that message so that you know how to correct it when you forget the outer class name.
By declaring the inner class after the outer class, you can now employ the second trick. The idea is to write the inner class so that only the outer class can access the members. To accomplish this task, you make all the members of the inner class either private or protected and then make the outer class, EthicalCompetition
, a friend of the inner class, Connection
. Here’s the modified version of the Connection
class:
class EthicalCompetition::Connection {
protected:
friend class EthicalCompetition;
void Connect();
};
Only the outer class can access most of the Connection
members now. However, even though the members are protected, nothing stops users outside EthicalConnection
from creating an instance of the Connection
class. To add this security, you employ the third trick, which is to create a constructor for the class that is either private or protected. When you change the constructor’s access, following suit with a destructor is a good idea. Make the destructor private or protected, too. Even if the constructor and destructor don’t do anything, making them private or protected prevents others from creating an instance of the class — others, that is, except any friends to the class. So here’s yet one more version of the class:
class EthicalCompetition::Connection {
protected:
friend class EthicalCompetition;
void Connect();
Connection() {}
~Connection() {}
};
This third trick completes the process. When someone tries to make an instance of the class outside EthicalCompetition
(such as in main()
), as in this:
EthicalCompetition::Connection myconnect;
you see the following message:
EthicalCompetition::Connection::~Connection()' is protected
You can still create an instance from within the methods of EthicalCompetition
. The ProtectingEmbedded
example, shown in Listing 4-5, contains the final application.
LISTING 4-5: Protecting Embedded Classes
#include <iostream>
using namespace std;
class EthicalCompetition {
private:
class Connection;
public:
void HardWork();
};
class EthicalCompetition::Connection {
protected:
friend class EthicalCompetition;
void Connect();
Connection() {}
~Connection() {}
};
void EthicalCompetition::HardWork() {
Connection c;
c.Connect();
cout << "Connected" << endl;
}
void EthicalCompetition::Connection::Connect() {
cout << "Connecting…" << endl;
}
int main(int argc, char *argv[]) {
// Uncomment this line to see the access error.
// EthicalCompetition::Connection myconnect;
EthicalCompetition comp;
comp.HardWork();
return 0;
}
Here’s the output from this example:
Connecting…
Connected
When you declare a type, such as an enum
, associating it with a class can be convenient. For example, you may have a class called Cheesecake
. In this class, you may have the SelectedFlavor
property, which can be an enumerated type, such as Flavor
:
enum Flavor {
ChocolateSuicide,
SquishyStrawberry,
BrokenBanana,
PrettyPlainVanilla,
CoolLuah,
BizarrePurple
};
Use this code to associate Flavor
with a class:
class Cheesecake {
public:
enum Flavor
{
ChocolateSuicide, SquishyStrawberry, BrokenBanana,
PrettyPlainVanilla, CoolLuah, BizarrePurple
};
Flavor SelectedFlavor;
int AmountLeft;
void Eat() {
AmountLeft = 0;
}
};
You can use the Flavor
type anywhere in your application, but to use it outside the Cheesecake
class, you must fully qualify its name by lining up the class name, two colons, and then the type name, like this:
Cheesecake::Flavor myflavor = Cheesecake::CoolLuah;
An enum
requires that you also fully qualify the enumeration. Using just CoolLuah
on the right side of the equals sign will cause the compiler to complain and say that CoolLuah
is undeclared. The Cheesecake
example, shown in Listing 4-6, demonstrates how we can use the Cheesecake
class.
LISTING 4-6: Using Types within a Class
#include <iostream>
using namespace std;
class Cheesecake {
public:
enum Flavor {
ChocolateSuicide, SquishyStrawberry, BrokenBanana,
PrettyPlainVanilla, CoolLuah, BizarrePurple
};
Flavor SelectedFlavor;
int AmountLeft;
void Eat() {
AmountLeft = 0;
}
};
int main() {
Cheesecake yum;
yum.SelectedFlavor = Cheesecake::SquishyStrawberry;
yum.AmountLeft = 100;
yum.Eat();
cout << yum.AmountLeft << endl;
return 0;
}
In contrast to nested classes, you can make a type within a class private or protected. If you do so, you can use the type only within the class members. If you try to use the type outside the class (including setting a property, as in yum.SelectedFlavor = Cheesecake::SquishyStrawberry;)
, you get a compiler error.
class Spongecake {
public:
typedef int SpongeNumber;
SpongeNumber weight;
SpongeNumber diameter;
};
int main() {
Spongecake::SpongeNumber myweight = 30;
Spongecake fluff;
fluff.weight = myweight;
return 0;
}