Advanced Class Usage - Advanced Programming - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book IV

Advanced Programming

Chapter 4: Advanced Class Usage

In This Chapter

Using polymorphism effectively

Adjusting member access between private, protected, and public when deriving new classes

Multiple-inheriting new classes

Making virtual inheritance work correctly

Keeping your friends straight, especially in class

Putting one class or type inside another

Classes are amazingly powerful. You can do so much with them. In this chapter, we talk about 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++ will greatly improve, and you will want to use many of these topics throughout your programming.

We also talk about many issues that come up when you are deriving new classes and inheriting members. This discussion includes virtual inheritance and multiple inheritance, topics that people mess up a lot. We describe the ways you can put classes and types inside other classes, too.

Inherently Inheriting Correctly

Without inheritance, doing object-oriented programming 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 mess things up. In the sections that follow, we talk about different ways to use inheritance and how to keep it all straight.

Morphing your inheritance

Polymorphism refers to using one 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: When you say a “pointer to a pointer,” the first pointer really means “the address of the second pointer variable.” We need to phrase things like that, or our brains might explode under certain situations.)

So if you have a function such as this:

void Feed(Creature *c)

{

cout << “Feed me!” << endl;

}

you are free to pass the address of a Platypus object, as in the following:

Platypus *plato = new Platypus;

Feed(plato);

With a function that takes the address of a pointer variable, like this:

void Eat(Creature **c)

{

cout << “Feed me!” << endl;

}

(note the two asterisks in the parameter), you cannot pass the address of a pointer to a Platypus instance, as in this example:

Platypus *plato = new Platypus;

Eat(&plato);

If you try to compile this code, you get a compiler error.

You don’t always use polymorphism when you declare a variable. If you do, you would be declaring variables like this:

Creature *plato = new Platypus;

The type plato is a pointer to Creature. But the object is a Platypus. You can do this 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 member function of Platypus — you can use plato to call only members of Creature! 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 would not work:

Creature *plato = new Platypus;

plato->SingLikeABird();

Although the first line would compile, the second wouldn’t. When the compiler gets to the second line, it thinks that plato is only an object of class type Creature. And Creature does not have a member 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 save work and start by declaring plato as what it is:

Platypus *plato = new Platypus;

plato->SingLikeABird();

You may need to do it at times. For example, you may have a variable that can hold an instance of an object or its derived object. Then you would have to use polymorphism, as in the following code:

Creature *plato;

if (HasABeak == true)

{

plato = new Platypus;

}

else

{

plato = new Creature;

}

In this code, we have a pointer to Creature. In that pointer, we store 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, then the Creature instance won’t have a SingLikeABird member function. Either you get some type of error message when you run the program or you don’t, but the program will mess up later. And those messing-up-later errors are the worst kind to try to fix.

Adjusting access

You may have a class that has protected members; and in a derived class, you may want to make the member public. You do this by adjusting the access. You have two ways to do this: One is the older way, and the other is the newer ANSI way. 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;

};

We declared the member X: We used the base classname, two colons, and then the member name. We didn’t include any type information; that was implied. So in the class Secret, the member X is protected. But in Revealed, it is public.

Here’s the ANSI way. We’ve thrown in the word using. Otherwise, it’s the same:

class Secret

{

public:

int X;

};

class Revealed : public Secret

{

public:

using Secret::X;

};

And now, when you use the Revealed class, the inherited member X is public (but X is still protected in the base class, Secret).

image 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 member variables of the same name within the class; and needless to say, that can be confusing! Take a 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 . . . which variable? The inherited X, because SetX is part of the base class! The third line stores 10 in . . . which one? The new X declared in the derived class! So then GetX is again part of the base class, but will it print 10 or 30? It will print 10!

Personally, having two member variables of the same name is downright confusing. (Fortunately, our brains didn’t quite explode because we’re still here, writing away.) We think that it would be best if the compiler didn’t allow you to have two variables of the same name. But even though the compiler allows it, that 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 program.

imageNow think about this: 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 could 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;

};

Notice what we did: We derived the class as protected, as you can see in the header line for the AddedSecurity class. That means all the inherited public members of Secret will be protected in the derived class. But then we promoted Magic back to public by adjusting its member access. Thus, after all is said and done, Magic is the only public member of AddedSecurity. All the rest are protected.

image If you have a member that is private and you try to adjust its access to protected or public in a derived class, you quickly discover that the compiler won’t let you do it. The reason is that the derived class doesn’t even know about the member because the member is public. And because the derived class doesn’t know about the member, you can’t adjust its access.

Returning something different, virtually speaking

Two words that sound similar and have similar meanings but are, nevertheless, different are overload and override. Although both words appear in movies (“Danger, danger! The system is overloaded, so we need to override the built-in security!”), they’re less glamorous in computer programming. But the real danger that results in an overloading of your brain is in confusing the two words where one meaning overrides the other. Whew!

So first, let us clarify: 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.

To overload means to take a function and write another function of the same name that takes a different set of parameters. An overloaded function can optionally return a different type, but the parameters must be different, 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 would 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.

In this section, we talk about one particular issue dealing with overriding functions (that is, replacing a function in a derived class). We said something a few paragraphs back that many others have said: We mentioned that the function must have the same parameter types and must return the same type.

imageA situation exists under which you can violate this rule, although only slightly. You can violate the rule of an overridden function returning the same type as the original function if all three of the following are true:

♦ The overridden function returns an instance of a class derived from the type returned by the original function.

♦ You return either a pointer or a reference, not an object.

♦ If you return a pointer, the pointer doesn’t refer to yet another pointer.

imageTypically, you want to use this approach when you have a container class that holds multiple instances of another class. For example, you may have a class called Peripheral. You may also have a container class called PeripheralList, which holds instances ofPeripheral. You may later derive a new class from Peripheral, called Printer, and a new class from PeripheralList, called PrinterList. If PeripheralList has a function that returns an instance of Peripheral, you would override that function inPrinterList. But instead of having it return an instance of Peripheral, you would have it return an instance of Printer.

We did exactly this in Listing 4-1.

Listing 4-1: Overriding and Returning a Derived Class

#include <iostream>

#include <string>

#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) : Type(atype),

Peripheral(aname, aprice, aserial) {}

};

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;

}

We used 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 then retrieve items from the map based on the key. In this example, we’re storing peripherals (the value) based on a name, which is a string (the key). To create the map, we use a typedef and specify the two types involved: first the key and then the value. The key is a string, and the value is a pointer to Peripheral. The typedef, then, looks like this:

typedef map<string, Peripheral *> PeripheralMap;

This creates a type of a map that enables us to store a set of Peripheral instances, and we can look them up based on a name. To put an item in the map, we use a notation similar to that of an array, 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, we simply refer to the map using brackets again, as in this line from the listing:

return list[name];

In Listing 4-1, we have a Peripheral class, and from that we derive a Printer class. We then have a container class that we created called PeripheralList, and from that we derived a class called PrinterList. The idea is that the PrinterList holds only instances of the class called Printer. So in the code, we overrode the GetPeripheralByName function. The version inside PrinterList casts the item to a Printer. We did this because the items in the list are instances of PeripheralList. But if we were to leave this function as is, then every time we want to retrieve a Printer, we would get back a pointer to a Peripheral instance, and we would have to cast it to a (Printer *) type. But that’s annoying. We don’t want to have to do that every time because we’re lazy. Instead, we overrode theGetPeripheralByName function and did the cast right in there.

Even though we overrode it, we’re allowed to return from the function a slightly different (but related) type. And it works!

image The code in Listing 4-1 has a small bug: Nothing is stopping you from putting an instance of Peripheral in the PrinterList container. Or, for that matter, you could put an instance of any other class derived from Peripheral if there were more. But when we retrieve the instance in the GetPeripheralByName, we automatically cast it to a Printer. That would be a problem if somebody had stuffed something else in there other than a Printer instance. To prevent that, create a special AddPeripheral function for the PrinterListclass that takes, specifically, a Printer. To do that, you would make the AddPeripheral function in PrinterList virtual and then override it, modifying the parameter to take a Printer rather than a Peripheral. When you do so, you will hide the function in the base class. But that’s okay: You don’t want people calling that one because that can take any Peripheral, not just a Printer instance. When you run this application, you should get an output value of 80.

Multiple inheritance

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 you want in the same derived class. If that’s the case, you can inherit from both through multiple inheritance.

image Multiple inheritance is messy and difficult to pull off properly. But when used with care, you can make it work.

Listing 4-2 is a complete example of 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 this 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 allowed us to call both functions for the instance child. Also notice how we caused that to happen:

class Derived : public Mom, public Dad

We put the base classes to the right of the single colon as with a single inheritance, and we separated the classes with a comma. We also preceded each class with the type of inheritance, in this case public.

imageAs 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

What this means is that the public members that are derived from Dad are now protected in the class called Derived. This means users cannot call the member functions inherited from Dad, nor can they access any member variables inherited from Dad. If you used this type of inheritance in Listing 4-2, this line would no longer be allowed:

child.Beauty();

If you try to compile it, you will see the following error, because the Beauty member is protected now:

`void Dad::Beauty()’ is inaccessible

image When you are working with multiple inheritance, be careful that you understand what your code is doing. Although it may compile correctly, it still may not function correctly. That leads to the famous creepy crawling thing called a bug.

Strange, bizarre, freaky things can happen with multiple inheritance. What if both base classes have a member variable called Bagel. What happens if you multiply derive a class from both of these classes? The answer is this: The compiler gets confused. Suppose you enhance the two base classes with a Bagel effect:

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 CodeBlocks:

Error: request for member `Bagel’ is ambiguous

Aha! We’re being ambiguous! It means the compiler isn’t sure which Bagel we’re referring 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. Now this is going to look bizarre, but we promise that it’s correct. Suppose we’re referring to the Bagel inherited from Mom. Then we can put the name Mom before the word Bagel, separated by two colons:

child.Mom::Bagel = 42;

Yes, that really is correct, even though it seems a little strange. And if we want to refer to the one by Dad, we do this:

child.Dad::Bagel = 17;

Both lines compile properly because we’re removing any ambiguities.

image When you use multiple inheritance, remove any ambiguities by specifying the name of the base class. But don’t worry if you forget: The compiler will give you an error message because it won’t know which item you’re referring to.

Virtual inheritance

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 where you have a diamond-shaped inheritance, as in Figure 4-1.

Figure 4-1:Using diamond inheritance can be hard.

image

In Figure 4-1, you can see the base class is Rock. From that we derived two classes, Diamond and Jade. So far, so good. But then something strange happened: We used multiple inheritance to derive a class MeltedMess from Diamond and Jade. Yes, you can do this. But you have to be careful.

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, you get an ambiguously melted mess: The compiler claims that it doesn’t know which Weight you are referring to: the one inherited from Diamond or the one inherited from Jade. Now you know and we know that there should only be one instance of Weight, because it came from a single base class, Rock. But the compiler sees the situation differently and has trouble with it.

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. Now 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 for anything MeltedMess added that wasn’t inherited.

But remember, Diamond is derived from Rock. Therefore, Diamond has a portion inside it that is a Rock. Similarly, Jade is derived from Rock. That means Jade has a portion inside it that is a Rock.

And melting all these thoughts together, if an instance of MeltedMess has both a Diamond in it and a Jade in it, and each of those in turn has a Rock in it, then by the powers of logic vested in us, we declare that MeltedMess must have two Rocks in it! And with each Rock comes a separate Weight instance. Listing 4-3 shows the problem. In this listing, we declare the classes Rock, Diamond, Jade, and MeltedMess.

Listing 4-3: Cracking up 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;

}

There is one member called Weight, and it’s part of Rock. In the Diamond class, we included two accessor methods, one to set the value of Weight and one to get it. We did the same thing in the Jade class.

We derived the class MeltedMess from both Diamond and Jade. We created an instance of it and called the four member functions that access the Weight member. First, we called the one for Diamond, setting Weight to 10. Then we called the one for Jade, setting the Weightto 20.

In a perfect world where each object only has one Weight, this would have first set the Weight to 10 and then to 20. When we print it, we should see 20 both times. But we don’t. Here’s the output:

10

20

When we asked the Diamond portion to cough up its Weight, we saw 10. But when we asked the Jade portion to do the same, we saw 20. They’re different! Therefore, we have two different Weight members. That’s not a good thing.

To fix it, add the word virtual when you inherit it. According to the ANSI standard, you put virtual in the two middle classes. In our case, that means Diamond and Jade. Thus, you need to modify the class headers back in Listing 4-3 for Diamond and Jade to look like this:

class Diamond : virtual public Rock {

class Jade : virtual public Rock {

Polymorphism with multiple inheritances

If you have multiple inheritance, you can safely treat your object as any of the base classes. In the case of the diamond example, you can treat an instance of MeltedMess as a Diamond instance or as a Jade instance. For example, if you have a function that takes as a parameter a pointer to a Diamond instance, you can safely pass a pointer to a MeltedMess instance. Casting also works: You can cast a MeltedMess instance to a Diamond instance or to a Jade instance. However, if you do, we suggest using the static_cast keyword, rather than the old C-style casts where you simply put the type name in parentheses before the variable you are casting.

When you do that and then run the program, you find that you have only one instance of Weight in the final class MeltedMess! It’s not such a mess after all! Here’s the output after we made the change:

20

20

Now this makes sense: Only one instance of Weight is in the MeltedClass 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);

Then the following line prints the value of the one Weight instance, 20:

cout << mymess.GetDiamondWeight() << endl;

The following line again prints the value of the one Weight instance!

cout << mymess.GetJadeWeight() << endl;

image With a diamond inheritance, use virtual inheritance in the middle classes to clean them. Although you can also add the word virtual to the final class (in the example’s case, that’s MeltedClass), you don’t need to.

Friend classes and functions

You may encounter a situation where you want one class to access the private and protected members of another class.

Friends of a same class

Can an instance of a class access the private and protected members of other instances of the same class? Yes, the compiler allows you to do it. Should you? That depends on your situation. Now think about how you would do that: Inside a member function for a class you would have a pointer to another instance of the same class, perhaps passed in as a parameter. The member function is free to modify any of its members or the object passed in. For your situation, you may need to use friend classes, but always be careful.

Normally, this isn’t allowed. But it is if you make the two classes best friends. Okay, that sounds corny, but C++ gives us that word: friend. Only use friend 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 will allow the member functions in Square to access the private and protected members of any instance of type DrawingBoard. (Remember that ultimately we’re talking about instances here.)

imageThe friend word is powerful because it allows object two to grind up object three, possibly against the will of object one (the object that created object three). And that can create bugs! As Grandma always warned us, “Please use discretion when picking your friends, especially when writing object-oriented programming code in C++.”

image Some compilers will let you declare a friend without the word class, like so:

friend Square;

However, the gnu compiler under CodeBlocks doesn’t allow this. Instead, you must use the class keyword:

friend class Square;

Using Classes and Types within Classes

Sometimes a program 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.

Nesting a class

You may have times when you create a set of classes, and in the set you have a primary class that people will be using, while the other classes are 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 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 programs. You’re just writing a set of classes; the teammates are writing the rest of a program.

In the classes you are 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 will not directly create instances of. One might be a class called Connection that handles the tasks of connecting to the competitor’s computer.

Here’s the first problem: The class Connection may be something you write, but another class somewhere may be called Connection, and your coworkers might need to use that class. And here’s the second problem: If you have that Connection class, you may not want your coworkers using it. 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 a bit more unique, such as EthicalCompetitionConnection. But that’s a bit long. And why go through all that trouble if it’s not even going to be used except internally in the code for EthicalCompetition? However, you could shorten the classname 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 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 we wrote a class inside a class. We did not provide the code for the functions themselves, so here they are:

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 ConnectionClass requires first the outer classname, then two colons, then the inner classname, then two colons again, and finally the function name. This follows the pattern you normally use where you put the classname first, then two colons, and then the function name. But in this case, you have two classnames separated with a colon.

If you want to declare an instance of the Connection class, you do it differently, depending on where you are when you declare it. By that we don’t mean whether you’re in an office cube or sitting on the beach with a laptop; rather, we mean where in the code you are trying to declare an instance.

If you are inside a member function of the outer class, EthicalCompetition, you simply refer to the class by its name, Connection. You can see we did that in the member function HardWork, with this line:

Connection c;

If you’re outside the member functions, you can declare an instance of the inner class, Connection, without an instance of the outer class, EthicalCompetition. To do this, you fully qualify the classname, like this:

EthicalCompetition::Connection myconnect;

This line would go, for instance, in the main of your program 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 nosey 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.

imageIn a sense, so far, you’ve created a handy grouping of the class, and you also set your grouping up so 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.

You can add higher security to a class that will prevent others from using your inner class. For that, you use a few tricks.

image Don’t put the inner class definition inside a private or protected section of the outer class definition. It doesn’t work.

Here’s how you create the inner class definition. For the first trick, we need to show you how you can declare the class with a forward definition but put the class definition outside the outer class. The following code does this:

class EthicalCompetition

{

private:

class Connection;

public:

void HardWork();

};

class EthicalCompetition::Connection

{

public:

void Connect();

};

Inside the outer class, we wrote a header for the inner class and a semicolon instead of writing the whole inner class; that’s a forward declaration. Then we wrote the rest of the inner class after the outer class. To make this work, we had to again fully qualify the name of the class, like this:

class EthicalCompetition::Connection

imageIf we skipped the word EthicalCompetition and two colons, the compiler compiles this class like a different class. Later, the compiler would complain that it can’t find the rest of the declaration for the ConnectionClass. The error is aggregate ‘class EthicalCompetition::Connection c’ has incomplete type and cannot be initialized. Remember that message, so you know how to correct it when you forget the outer classname.

By declaring the inner class after the outer class, you can now employ another trick. The idea is to write it so that only the outer class can access the members. To accomplish this, you can 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 its members. But we say most because we left something out. Although the members are protected, nothing stops users outside EthicalConnection from creating an instance of the class. To add this security, you need a constructor for the class that is either private or protected. And when you do that with a constructor, 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, by making them private or protected you prevent 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 does the trick. When we try to make an instance of the class outside EthicalCompetition (such as in main), as in this:

EthicalCompetition::Connection myconnect;

we see the message

EthicalCompetition::Connection::~Connection()’ is protected

Yet, we can still make an instance from within the member functions of EthicalCompetition. It worked! Listing 4-4 shows the final program.

Listing 4-4: 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[])

{

// EthicalCompetition::Connection myconnect;

EthicalCompetition comp;

comp.HardWork();

return 0;

}

We purposely left in a commented-out line where we attempted to make an instance of the inner class, Connection. Previously, we had the line there, but not commented out, so we could see what error message it would print and then tell you about it. If you want to see the error message, you can remove the two slashes so the compiler will try to compile the line.

Types within classes

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 member variable SelectedFlavor. The SelectedFlavor member can be your own enumerated type, such as Flavor, like this:

enum Flavor

{

ChocolateSuicide,

SquishyStrawberry,

BrokenBanana,

PrettyPlainVanilla,

CoolLuah,

BizarrePurple

};

To associate these with a class, you can put them in a class, like this:

class Cheesecake

{

public:

enum Flavor

{

ChocolateSuicide, SquishyStrawberry, BrokenBanana,

PrettyPlainVanilla, CoolLuah, BizarrePurple

};

Flavor SelectedFlavor;

int AmountLeft;

void Eat()

{

AmountLeft = 0;

}

};

The type Flavor now can be used anywhere in your program, but to use it outside the member functions of the Cheesecake class, you must fully qualify its name by putting the classname, two colons, then the type name like this:

Cheesecake::Flavor myflavor = Cheesecake::CoolLuah;

As you can see, for an enum, we also had to fully qualify the enumeration itself. If we had just put CoolLuah on the right side of the equals sign, the compiler would complain and say that CoolLuah is undeclared.

Listing 4-5 shows an example of where we use the Cheesecake class.

Listing 4-5: 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;

}

Notice in Listing 4-5 that I had to fully qualify the name SquishyStrawberry.

image When you declare a type (using a typedef or an enum) inside a class, you do not need an instance of the class present to use the type. But you must fully qualify the name. Thus, you can set up a variable of type Cheesecake::Flavor and use it in your program without creating an instance of Cheesecake.

imageDo you have types that you want used only within the class? Make them protected or private. That way, you cannot use them outside the class.

Unlike nested classes, you can make a type within a class private or protected. If you do, you can use the type only within the member functions of the class. If you try to use the type outside the class (including setting a member variable, as in yum.SelectedFlavor = Cheesecake::SquishyStrawberry;), you get a compiler error.

imageYou can also put a typedef inside your class in the same way you would put an enum, as in the following example:

class Spongecake

{

public:

typedef int SpongeNumber;

SpongeNumber weight;

SpongeNumber diameter;

};

int main(int argc, char *argv[])

{

Spongecake::SpongeNumber myweight = 30;

Spongecake fluff;

fluff.weight = myweight;

return 0;

}