接着上一篇《友元是什么》中,我们发现Remote友元类的大多数方法都是用Tv类的公有接口实现。这意味着这些方法并不是真正需要友元。
事实上唯一直接访问Tv成员的Remote方法是Remote::set_chan(),因此它是唯一需要作为友元的方法。
确实可以仅让特定的类成员成为另一类的友元。
这种做法稍微有点麻烦,必须小心排列各种声明和定义的顺序。
让Remote::set_chan()成为Tv类的友元的方法是,在Tv类声明中将其声明为友元:
class Tv
{
friend void Remote::set_chan(Tv & t, int c);
}
要让编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是一个类方法;
这就意味着要把Remote的定义放到Tv类定义之前。
但是Remote方法提到了Tv对象,而这就意味着Tv定义应当放在Remote定义之前。
这就产生了循环依赖的问题。要避免循环依赖关系,就要使用前向声明(forward declaration)。
解决方法如下:
class Tv; //forward declaration 告诉编译器Tv是一个类
class Remote {...}; //然后再Remote中出现set_chan 方法时,知道其中Tv是一个类
class Tv {...};
//这里补充一句,让整个Remote类成为友元并不需要前向声明,因为友元语句本身已经指出Remote是一个类;
friend class Remote;
但是能否像下面这样排列呢?
class Remote ; //forward declaration
class Tv {...};
class Remote {...};
答案是不能,因为在编译器看到Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应先看到Remote类的声明和set_chan()方法的声明。
还有一个麻烦就是。Remote声明中包含了内联代码
void onoff(Tv & t) {t.onoff();}
由于这将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明。这样才能知道Tv有哪些方法,但正如看到的,该声明位于Remote声明的后面。
这种问题的解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后。
class Tv; //forward declaration
class Remote {...}; //Tv-using methods as prototypes only 只包含方法的声明
class Tv {...};
//put Remote method definitions here 定义在这里写
Remote方法的声明与下面类似
void onoff(Tv & t);
检查该原型时,编译器都需要知道Tv是一个类,而向前声明提供了这样的信息。
当编译器到达真正的方法定义时,它已经读取到了Tv类的声明,并拥有编译这些方法的所需信息。
通过在方法定义中使用inline关键字,仍然可以使其成为内联方法。
//这种友元成员函数的声明、定义顺序非常微妙,令人抓狂。很容易造成错误,一旦问题复杂起来,定位bug都很困难。难怪C++是个大坑。友元的存在就是其中一个大坑。把类的关系,函数的关系搞复杂了。
tvfm.h
1 #ifndef TVFM_H_ 2 #define TVFM_H_ 3 4 class Tv; //forward declaration 5 6 class Remote 7 { 8 public: 9 enum State{Off,On}; 10 enum {MinVal, Maxval=20}; 11 enum {Antenna, Cable}; 12 enum {TV, DVD}; 13 14 private: 15 int mode; 16 17 public: 18 Remote(int m = TV):mode(m) {} 19 bool volup(Tv & t); 20 bool voldown(Tv & t); 21 bool onoff(Tv & t); 22 bool chanup(Tv & t); 23 bool chandown(Tv & t); 24 void set_mode(Tv & t); 25 void set_input(Tv & t); 26 void set_chan(Tv & t, int c); 27 }; 28 29 class Tv 30 { 31 public: 32 friend void Remote::set_chan(Tv & t, int c); 33 enum State{Off, On}; 34 enum {Minval, Maxval =20}; 35 enum {Antenna, Cable}; 36 enum {Tv, DVD}; 37 38 Tv(int s=Off, int mc=125):state(s),volume(5),maxchannel(mc),channel(2),mode(Cable),input(TV) {} 39 void onoff() {state = (state==On)?Off:On;} 40 bool ison() const {return state == On;} 41 bool volup(); 42 bool voldown(); 43 void chanup(); 44 void chandown(); 45 void set_mode() {mode = (mode == Antenna)?Cable:Antenna;} 46 void set_input() {input = (input == TV)?DVD:TV;} 47 void settings() const; 48 49 private: 50 int state; 51 int volume; 52 int channel; 53 int maxchannel; 54 int mode; 55 int input; 56 }; 57 58 //Remote methods as inline functions 59 inline bool Remote::volup(Tv & t) {return t.volup();} 60 inline bool Remote::voldown(Tv & t) {return t.voldown();} 61 inline void Remote::onoff(Tv & t) {t.onoff();} 62 inline void Remote::chanup(Tv & t) {t.chanup();} 63 inline void Remote::chandown(Tv & t) {t.chandown();} 64 inline void Remote::set_mode(Tv & t) {t.set_mode();} 65 inline void Remote::set_input(Tv & t) {t.set_input();} 66 inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}