友元类使用的一些问题
bigfly Lv4

为什么会有友元类?

与友元函数存在的功能类似 ,当两个或多个类之间不存在派生或者继承关系的时候,但又想在某一个类中调用另外一个类的方法,此时可以用 友元类。友元类的所有方法都可以访问原始类的私有成员和保护成员。

循环依赖:

这里创建两个类,一个是电视类、另外一个是遥控器类。并将遥控器类作为电视类的友元类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#ifndef __TV_H__
#define __TV_H__

#include <iostream>

using namespace std;

class Tv
{
private:
enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};

int state;
int volume;
int channel;
int input;

public:
Tv(int s = off) : state(s), volume(5), channel(2), input(TV){}
void onoff(){state = (state == on) ? off : on;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_input(){input = (input == TV) ? DVD : TV;}
void show_settings() const;

friend class Remote;
};

class Remote
{
private:
int mode;
public:
Remote(int m = Tv::TV) : mode(m){}
void onoff(Tv &t){t.onoff();}
bool volup(Tv &t){return t.volup();}
bool voldown(Tv &t){return t.voldown();}
void chanup(Tv &t){t.chanup();}
void chandown(Tv &t){t.chandown();}
void set_channel(Tv &t, int c){t.channel = c;}
void set_input(Tv &t){t.set_input();}
};

#endif

上面这个类书写存在的一些问题:

只在Remote的void set_channel(Tv &t, int c){t.channel = c;}中访问到了Tv类的私有成员channel ,其他的都是在调用Tv类的公有接口,那这样的话将Remote定义为友元类就大可不必,可用只将Remote中的set_channel设置为友元就可用,做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#ifndef __TV_H__
#define __TV_H__

#include <iostream>

using namespace std;

class Tv
{
private:
enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};

int state;
int volume;
int channel;
int input;

public:
Tv(int s = off) : state(s), volume(5), channel(2), input(TV){}
void onoff(){state = (state == on) ? off : on;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_input(){input = (input == TV) ? DVD : TV;}
void show_settings() const;

friend void Remote::set_channel(Tv &t, int c);
};

class Remote
{
private:
int mode;
public:
Remote(int m = Tv::TV) : mode(m){}
void onoff(Tv &t){t.onoff();}
bool volup(Tv &t){return t.volup();}
bool voldown(Tv &t){return t.voldown();}
void chanup(Tv &t){t.chanup();}
void chandown(Tv &t){t.chandown();}
void set_channel(Tv &t, int c){t.channel = c;}
void set_input(Tv &t){t.set_input();}
};

#endif

上面程序存在的问题?

存在的问题是循环依赖,解决方法是前向声明。

循环依赖解释?

类Tv在编译的时候会调用friend void Remote::set_channel(Tv &t, int c);而此时不知道,Remote是什么也不知道其中的set_channel方法是什么。

倘若将两个类交换顺序能否解决问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#ifndef __TV_H__
#define __TV_H__

#include <iostream>

using namespace std;

class Tv;

class Remote
{
private:
int mode;
public:
Remote(int m = Tv::TV) : mode(m){}
void onoff(Tv &t){t.onoff();}
bool volup(Tv &t){return t.volup();}
bool voldown(Tv &t){return t.voldown();}
void chanup(Tv &t){t.chanup();}
void chandown(Tv &t){t.chandown();}
void set_channel(Tv &t, int c){t.channel = c;}
void set_input(Tv &t){t.set_input();}
};


class Tv
{
private:
enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};

int state;
int volume;
int channel;
int input;

public:
Tv(int s = off) : state(s), volume(5), channel(2), input(TV){}
void onoff(){state = (state == on) ? off : on;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_input(){input = (input == TV) ? DVD : TV;}
void show_settings() const;

friend void Remote::set_channel(Tv &t, int c);
};


#endif

即使交换顺序并且加上class Tv声明能解决问题吗?

答案是否定的因为Remote类中的构造函数 用到了 Tv::TV 即便前面声明了Tv是类,但是此时也无法知道Tv中又什么成员变量(即无法知道TV是什么)

解决办法 将Tv类中的枚举类型在 Remote类中也来一份,并且使用Remote自己的枚举成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#ifndef __TV_H__
#define __TV_H__

#include <iostream>

using namespace std;
class Tv;

class Remote
{
private:
int mode;

enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};
public:
Remote(int m = TV) : mode(m){}

void onoff(Tv &t){t.onoff();}
bool volup(Tv &t){return t.volup();}
bool voldown(Tv &t){return t.voldown();}
void chanup(Tv &t){t.chanup();}
void chandown(Tv &t){t.chandown();}
void set_channel(Tv &t, int c){t.channel = c;}
void set_input(Tv &t){t.set_input();}
};

class Tv
{
private:
enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};

int state;
int volume;
int channel;
int input;

public:
Tv(int s = off) : state(s), volume(5), channel(2), input(TV){}
void onoff(){state = (state == on) ? off : on;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_input(){input = (input == TV) ? DVD : TV;}
void show_settings() const;

friend void Remote::set_channel(Tv &t, int c);
};



#endif

上面这样写就没事了吗?

答案是否定的,存在问题,类Remote中成员函数实现中,调用了Tv类的方法,而此时Tv类没有编译,所以会报错。正确做法是,将Remote中接口的实现定义在类外面也就是 类Tv的后面。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#ifndef __TV_H__
#define __TV_H__

#include <iostream>

using namespace std;

class Tv;

class Remote
{
private:
int mode;

enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};
public:
Remote(int m = TV) : mode(m){}
void onoff(Tv &t);
bool volup(Tv &t);
bool voldown(Tv &t);
void chanup(Tv &t);
void chandown(Tv &t);
void set_channel(Tv &t, int c);
void set_input(Tv &t);
};

class Tv
{
private:
enum{off, on};
enum{MinVal, MaxVal = 20};
enum{MinChan = 1, MaxChan = 100};
enum{TV, DVD};

int state;
int volume;
int channel;
int input;

public:
Tv(int s = off) : state(s), volume(5), channel(2), input(TV){}
void onoff(){state = (state == on) ? off : on;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_input(){input = (input == TV) ? DVD : TV;}
void show_settings() const;

friend void Remote::set_channel(Tv &t, int c);
};

inline void Remote::onoff(Tv &t){t.onoff();}
inline bool Remote::volup(Tv &t){return t.volup();}
inline bool Remote::voldown(Tv &t){return t.voldown();}
inline void Remote::chanup(Tv &t){t.chanup();}
inline void Remote::chandown(Tv &t){t.chandown();}
inline void Remote::set_channel(Tv &t, int c){t.channel = c;}
inline void Remote::set_input(Tv &t){t.set_input();}

#endif

总结:

以上就是友元类,产生循环依赖的解决办法。思想来源c++primer plus 15章

以上是Remote 对Tv 做出一些控制 ,用的方法是将Remote声明为Tv的友元,且由于只有其中一个方法调用了Tv的私有成员,其余都是调用公有接口,那为了封装的安全性,只将Remote中调用Tv类中私有成员的方法声明为友元即可。而实际有可能存在互为友元的情况,即遥控器对电视做出控制,电视并给一个反馈到遥控器,这样就要求两个类互相为友元。

补充:

共同友元 ! 要使用友元的另一种情况是,函数需要访问两个类的私有数据。从逻辑上说,这样的函数是两个类的成员函数,但这是不可能的。做法就是共同友元。

应用场景:

有关类A是某可用编程的测量设备 ,B是某分析仪器,要求两者有共同的时钟,这样就可用声明两个友元函数,类A中将B 的成员函数声明为友元,类B中将A 的成员函数声明为友元,