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

 #ifndef TVFM_H_
#define TVFM_H_ class Tv; //forward declaration class Remote
{
public:
enum State{Off,On};
enum {MinVal, Maxval=};
enum {Antenna, Cable};
enum {TV, DVD}; private:
int mode; public:
Remote(int m = TV):mode(m) {}
bool volup(Tv & t);
bool voldown(Tv & t);
bool onoff(Tv & t);
bool chanup(Tv & t);
bool chandown(Tv & t);
void set_mode(Tv & t);
void set_input(Tv & t);
void set_chan(Tv & t, int c);
}; class Tv
{
public:
friend void Remote::set_chan(Tv & t, int c);
enum State{Off, On};
enum {Minval, Maxval =};
enum {Antenna, Cable};
enum {Tv, DVD}; Tv(int s=Off, int mc=):state(s),volume(),maxchannel(mc),channel(),mode(Cable),input(TV) {}
void onoff() {state = (state==On)?Off:On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna)?Cable:Antenna;}
void set_input() {input = (input == TV)?DVD:TV;}
void settings() const; private:
int state;
int volume;
int channel;
int maxchannel;
int mode;
int input;
}; //Remote methods as inline functions
inline bool Remote::volup(Tv & t) {return t.volup();}
inline bool Remote::voldown(Tv & t) {return t.voldown();}
inline void Remote::onoff(Tv & t) {t.onoff();}
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}

C++_友元2-友元成员函数的更多相关文章

  1. C++ 友元 (全局函数做友元) (类做友元) (成员函数做友元)

    1 //友元 全局函数做友元 2 /* 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 ...

  2. 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数

    1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...

  3. 重载运算符:类成员函数or友元函数

    类成员函数: bool operator ==(const point &a)const { return x==a.x; } 友元函数: friend bool operator ==(co ...

  4. C++学习之路—运算符重载(二)运算符重载作为类的成员函数和友元函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 对运算符重载的函数有两种处理方式:(1)把运算符 ...

  5. 友元(友元函数、友元类和友元成员函数) C++

    有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的.例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员. 友元(frend)机制允许一个类将 ...

  6. C++运算符重载三种形式(成员函数,友元函数,普通函数)详解

    首先,介绍三种重载方式: //作为成员函数重载(常见) class Person{ Private: string name; int age; public: Person(const char* ...

  7. C++运算符重载形式——成员函数or友元函数

    运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...

  8. C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数

    C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...

  9. c++友元函数、友元类、友成员函数

    友元函数:不是类成员函数,是一个类外的函数,但是可以访问类所有成员. class Point{ public: friend void fun(Point t);//友元函数 private: int ...

随机推荐

  1. 【bzoj2186】[Sdoi2008]沙拉公主的困惑

    2186: [Sdoi2008]沙拉公主的困惑 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 3303  Solved: 1129[Submit][S ...

  2. 面试题:java实例变量,局部变量,类变量 背1

    一.实例变量 也叫对象变量.类成员变量:从属于类由类生成对象时,才分配存储空间,各对象间的实例变量互不干扰,能通过对象的引用来访问实例变量.但在Java多线程中,实例变量是多个线程共享资源,要注意同步 ...

  3. Appium混合应用测试

    Appium测试混合应用 混合应用即是原生应用中间混着html页面,需要在两种类型的页面之间跳转. 测试Android混合应用 前期设置 4.4以下版本使用automationName:Selendr ...

  4. Action Results in MVC

  5. 《架构师杂志》评述:Scott Guthrie

    发布日期: 2007-03-29 | 更新日期: 2007-03-29   Scott Guthrie 是 Microsoft 开发事业部的总经理.他领导着负责构建 CLR(公共语言运行库).ASP. ...

  6. 【Android学习】实现卡片式ListView

    效果: 主要是设置xml文件 两种状态下的item card_background.xml <?xml version="1.0" encoding="utf-8& ...

  7. java中为什么要使用代理

    引入代理: 我们为什么要引入java的代理,除了当前类能够提供的功能外,我们还需要补充一些其他功能. 最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类, ...

  8. asp.net get图

    前段 <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat=& ...

  9. ASP.NET伪静态配置

    一.下载URLRewriter.dll 二.在项目中添加URLRewrite的引用 三.配置webconfig 1.在<configuration>节点中添加: <configSec ...

  10. 动态内存分配存在的问题(内存空洞)------c++程序设计原理与实践(进阶篇)

    new的问题究竟在哪里呢?实际上问题出在new和delete的结合使用上.考察下面程序中内存分配和释放过程: while(1){ Big* p=new big; //...... Small* n1= ...