When might you want to make one class a friend to another? Let’s look at an example. Suppose you must program a simple simulation of a television and a remote control. You decide to define a Tv
class representing a television and a Remote
class representing a remote control. Clearly, there should be some sort of relationship between these classes, but what kind? A remote control is not a television and vice versa, so the is-a relationship of public inheritance doesn’t apply. Nor is either a component of the other, so the has-a relationship of containment or of private or protected inheritance doesn’t apply. What is true is that a remote control can modify the state of a television, and this suggests making the Remote
class a friend to the Tv
class.
Let’s define the Tv
class. You can represent a television with a set of state members—that is, variables that describe various aspects of the television. Here are some of the possible states:
• On/off
• Channel setting
• Volume setting
• Cable or antenna tuning mode
• TV tuner or A/V input
The tuning mode reflects the fact that, in the United States, the spacing between channels for channels 14 and up is different for cable reception than it is for UHF broadcast reception. The input selection chooses between TV, which could be either cable or broadcast TV, and a DVD. Some sets may offer more choices, such as multiple DVD/Bluray inputs, but this list is enough for the purposes of this example.
Also a television has some parameters that aren’t state variables. For example, televisions vary in the number of channels they can receive, and you can include a member to track that value.
Next, you must provide the class with methods for altering the settings. Many television sets these days hide their controls behind panels, but it’s still possible with most televisions to change channels, and so on, without a remote control. However, often you can go up or down one channel at a time but can’t select a channel at random. Similarly, there’s usually a button for increasing the volume and one for decreasing the volume.
A remote control should duplicate the controls built in to the television. Many of its methods can be implemented by using Tv
methods. In addition, a remote control typically provides random access channel selection. That is, you can go directly from channel 2 to channel 20 without going through all the intervening channels. Also many remotes can work in two or more modes, for example, as a television controller and as a DVD controller.
These considerations suggest a definition like that shown in Listing 15.1. The definition includes several constants defined as enumerations. The following statement makes Remote
a friend class:
friend class Remote;
A friend declaration can appear in a public, private, or protected section; the location makes no difference. Because the Remote
class mentions the Tv
class, the compiler has to know about the Tv
class before it can handle the Remote
class. The simplest way to accomplish this is to define the Tv
class first. Alternatively, you can use a forward declaration; we’ll discuss that option soon.
// tv.h -- Tv and Remote classes
#ifndef TV_H_
#define TV_H_
class Tv
{
public:
friend class Remote; // Remote can access Tv private parts
enum {Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
Tv(int s = Off, int mc = 125) : state(s), volume(5),
maxchannel(mc), channel(2), mode(Cable), input(TV) {}
void onoff() {state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna)? Cable : Antenna;}
void set_input() {input = (input == TV)? DVD : TV;}
void settings() const; // display all settings
private:
int state; // on or off
int volume; // assumed to be digitized
int maxchannel; // maximum number of channels
int channel; // current channel setting
int mode; // broadcast or cable
int input; // TV or DVD
};
class Remote
{
private:
int mode; // controls TV or DVD
public:
Remote(int m = Tv::TV) : mode(m) {}
bool volup(Tv & t) { return t.volup();}
bool voldown(Tv & t) { return t.voldown();}
void onoff(Tv & t) { t.onoff(); }
void chanup(Tv & t) {t.chanup();}
void chandown(Tv & t) {t.chandown();}
void set_chan(Tv & t, int c) {t.channel = c;}
void set_mode(Tv & t) {t.set_mode();}
void set_input(Tv & t) {t.set_input();}
};
#endif
Most of the class methods in Listing 15.1 are defined inline. Note that each Remote
method other than the constructor takes a reference to a Tv
object as an argument. This reflects that a remote has to be aimed at a particular TV. Listing 15.2 shows the remaining definitions. The volume-setting functions change the volume member by one unit unless the sound has reached its minimum or maximum setting. The channel selection functions use wraparound, with the lowest channel setting, taken to be 1, immediately following the highest channel setting, maxchannel
.
Many of the methods use the conditional operator to toggle a state between two settings:
void onoff() {state = (state == On)? Off : On;}
Provided that the two state values are true
and false
, or, equivalently, 0
and 1
, this can be done more compactly by using the combined bitwise exclusive OR and assignment operator (^=
), as discussed in Appendix E, “Other Operators”:
void onoff() {state ^= 1;}
In fact, you could store up to eight bivalent state settings in a single unsigned char
variable and toggle them individually, but that’s another story, one made possible by the bitwise operators discussed in Appendix E.
// tv.cpp -- methods for the Tv class (Remote methods are inline)
#include <iostream>
#include "tv.h"
bool Tv::volup()
{
if (volume < MaxVal)
{
volume++;
return true;
}
else
return false;
}
bool Tv::voldown()
{
if (volume > MinVal)
{
volume--;
return true;
}
else
return false;
}
void Tv::chanup()
{
if (channel < maxchannel)
channel++;
else
channel = 1;
}
void Tv::chandown()
{
if (channel > 1)
channel--;
else
channel = maxchannel;
}
void Tv::settings() const
{
using std::cout;
using std::endl;
cout << "TV is " << (state == Off? "Off" : "On") << endl;
if (state == On)
{
cout << "Volume setting = " << volume << endl;
cout << "Channel setting = " << channel << endl;
cout << "Mode = "
<< (mode == Antenna? "antenna" : "cable") << endl;
cout << "Input = "
<< (input == TV? "TV" : "DVD") << endl;
}
}
Listing 15.3 is a short program that tests some of the features of the program so far. The same controller is used to control two separate televisions.
//use_tv.cpp -- using the Tv and Remote classes
#include <iostream>
#include "tv.h"
int main()
{
using std::cout;
Tv s42;
cout << "Initial settings for 42" TV:
";
s42.settings();
s42.onoff();
s42.chanup();
cout << "
Adjusted settings for 42" TV:
";
s42.settings();
Remote grey;
grey.set_chan(s42, 10);
grey.volup(s42);
grey.volup(s42);
cout << "
42" settings after using remote:
";
s42.settings();
Tv s58(Tv::On);
s58.set_mode();
grey.set_chan(s58,28);
cout << "
58" settings:
";
s58.settings();
return 0;
}
Here is the output of the program in Listings 15.1, 15.2, and 15.3:
Initial settings for 42" TV:
TV is Off
Adjusted settings for 42" TV:
TV is On
Volume setting = 5
Channel setting = 3
Mode = cable
Input = TV
42" settings after using remote:
TV is On
Volume setting = 7
Channel setting = 10
Mode = cable
Input = TV
58" settings:
TV is On
Volume setting = 5
Channel setting = 28
Mode = antenna
Input = TV
The main point to this exercise is that class friendship is a natural idiom in which to express some relationships. Without some form of friendship, you would either have to make the private parts of the Tv
class public or else construct some awkward, larger class that encompasses both a television and a remote control. And that solution wouldn’t reflect the fact that a single remote control can be used with several televisions.
3.142.114.19