1. 程式人生 > >C++_友元2之友元成員函數

C++_友元2之友元成員函數

之前 prot 由於 都是 中一 pes fin ant 有一個

接著上一篇《友元是什麽》中,我們發現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;}

C++_友元2之友元成員函數