C++_友元2-友元成员函数

接着上一篇《友元是什么》中,我们发现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;}

 

posted @ 2019-02-23 11:07  Grooovvve  阅读(209)  评论(0编辑  收藏  举报