Bootstrap

15 Friends, Expections, and More(友元类)

1 Friends

Several examples in this book so far use friend functions as part of the extended interface for a class. Such functions are not the only kinds of friends a class can have. A class also can be a friend. In that case,any method of the friend class can access private and protected members of the original class.Also you can be more restrictive and designate just particular member functions of a class to be friends to another class.A class defines which functions, member functions, or classes are friends; friendship cannot be imposed from the outside.Thus,although friends do grant outside access to a class’s private portion, they don’t really violate the spirit of object-oriented programming. Instead, they provide more flexibility to the public interface.

1.1 Friend Classes(友元类)

首先介绍一个使用友元类的例子。假设我们需要编写一个电视和遥控器的模拟程序,此时需要定义表示电视的Tv类和一个表示遥控器的Remote类。这两个类存在关系但并不属于同一类,因此无法使用公共继承。真实情况是遥控器可以修改电视的状态,这表明可以将Remote类设为Tv类的友元。

首先定义Tv类,下面是它的类属性:

  • On/off
  • Channel setting
  • Volume setting

接下来需要为类提供用于更改设置的方法,即修改Channel和Volume的方法。遥控器应该复制电视内置的控制功能。其许多方法可以通过使用电视的方法来实现。此外,遥控器通常提供随机接入频道选择功能(电视控制时候需要一个频道一个频道换台)。实现后的电视类和遥控器类如Listing 15.1所示,其中如下的语句将Remote声明为友元类:

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.

//Listing 15.1 tv.h
// 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};
	
	Tv(int s = Off, int mc = 125) : state(s), volume(5),
		maxchannel(mc), channel(2) {}
	void onoff() {state = (state == On)? Off : On;}
	bool ison() const {return state == On;}
	bool volup();
	bool voldown();
	void chanup();
	void chandown();
	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
};

class Remote{
public:
	Remote(){}
	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;}
};
#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.

//Listing 15.2 tv.cpp
// 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;
	}
}

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.

//Listing 15.3 use_tv.cpp
//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:\n";
	s42.settings();
	s42.onoff();
	s42.chanup();
	cout << "\nAdjusted settings for 42\" TV:\n";
	s42.settings();
	
	Remote grey;
	
	grey.set_chan(s42, 10);
	grey.volup(s42);
	grey.volup(s42);
	cout << "\n42\" settings after using remote:\n";
	s42.settings();
	
	Tv s58(Tv::On);
	s58.set_mode();
	grey.set_chan(s58,28);
	cout << "\n58\" settings:\n";
	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

42" settings after using remote:
TV is On
Volume setting = 7
Channel setting = 10

58" settings:
TV is On
Volume setting = 5
Channel setting = 28
1.2 Friend Member Functions

在前面代码的例子中,most of the Remote methods are implemented by using the public interface for the Tv class.This means that those methods don’t really need friend status. Indeed, the only Remote method that accesses a private Tv member directly is Remote::set_chan(), so that’s the only method that needs to be a friend. You do have the option of making just selected class members friends to another class rather than making the entire class a friend, but that’s a bit more awkward. You need to be careful about the order in which you arrange the various declarations and definitions. Let’s look at why.

The way to make Remote::set_chan() a friend to the Tv class is to declare it as a friend in the Tv class declaration:

class Tv{
	friend void Remote::set_chan(Tv & t, int c);
	...
};

However, for the compiler to process this statement, it needs to have already seen the Remote definition. Otherwise, it won’t know that Remote is a class and that set_chan() is a method of that class.This suggests putting the Remote definition above the Tv definition. But the fact that Remote methods mention Tv objects means that the Tv definition should appear above the Remote definition. Part of the way around the circular dependence is to use a forward declaration.To do so, you insert the following statement above the Remote definition:

class Tv; 	// forward declaration

This provides the following arrangement:

class Tv; 				// forward declaration
class Remote { 
	...
	bool volup(Tv & t) { return t.volup();} 
	...
};
class Tv{
	friend void Remote::set_chan(Tv & t, int c);
	...
};

注意我们不能使用下面的定义顺序,因为当编译器看到Remote方法在Tv类声明中被声明为友元时,编译器需要已经查看了Remote类的声明,特别是set_chan()方法的声明。

class Remote; 			// forward declaration
class Tv{
	friend void Remote::set_chan(Tv & t, int c);
	...
};
class Remote { 
	...
	bool volup(Tv & t) { return t.volup();} 
	...
};

Another difficulty remains. In Listing 15.1, the Remote declaration contains inline code such as the following:

void onoff(Tv & t) { t.onoff(); }

Because this calls a Tv method, the compiler needs to have seen the Tv class declaration at this point so that it knows what methods Tv has. But as you’ve seen, that declaration necessarily follows the Remote declaration.The solution to this problem is to restrict Remote to method declarations and to place the actual definitions after the Tv class.This leads to the following ordering:

class Tv; 					// forward declaration
class Remote { ... }; 		// Tv-using methods as prototypes only
class Tv { ... };
// put Remote method definitions here

The Remote prototypes look like this:

void onoff(Tv & t);

All the compiler needs to know when inspecting this prototype is that Tv is a class,and the forward declaration supplies that information. By the time the compiler reaches the actual method definitions, it has already read the Tv class declaration and has the added information needed to compile those methods. By using the inline keyword in the method definitions, you can still make the methods inline methods. Listing 15.4 shows the revised header file.

//Listing 15.4 tvfm.h
// tvfm.h -- Tv and Remote classes using a friend member
#ifndef TVFM_H_
#define TVFM_H_

class Tv; // forward declaration

class Remote{
public:
	enum State{Off, On};
	enum {MinVal,MaxVal = 20};

	Remote(){}
	bool volup(Tv & t); // prototype only
	bool voldown(Tv & t);
	void onoff(Tv & t) ;
	void chanup(Tv & t) ;
	void chandown(Tv & t) ;
	void set_chan(Tv & t, int c);
};

class Tv{
public:
	friend void Remote::set_chan(Tv & t, int c);
	enum State{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){}
	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;}
private:
	int state;
	int volume;
	int maxchannel;
	int channel;
};

// Remote methods as inline functions
inline bool Remote::volup(Tv & t) { return t.volup();}
inline bool Remote::voldown(Tv & t) { return t.voldown();}
inline void Remote::onoff(Tv & t) { t.onoff(); }
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;} 
#endif

If you include tvfm.h instead of tv.h in tv.cpp and use_tv.cpp, the resulting program behaves the same as the original.The difference is that just one Remote method—instead of all the Remote methods—is a friend to the Tv class.

By the way, making the entire Remote class a friend doesn’t need a forward declaration because the friend statement itself identifies Remote as a class:

friend class Remote;
1.3 Other Friendly Relationships

待补充 901页

;