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;}
C++_友元2之友元成員函數