接着上一篇《友元是什么》中,我们发现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. codeforce469DIV2——D. A Leapfrog in the Array

    题意: 给出1<=n<=10^18和1<=q<=200000,有一个长度为2*n-1的数组,初始时单数位置存(i+1)/2,双数位置是空的.每次找出最右边的一个数将它跳到离它最 ...

  2. Luogu 4869 albus就是要第一个出场

    BZOJ 2844 被NOIP模拟赛题弄自闭了QuQ. 因为本题要求异或,所以自然地构造出线性基,假设本题中给出的数有$n$个,而我们构造出的线性基大小为$m$,那么每一个可以异或出来的数相当于出现了 ...

  3. Django框架 之 querySet详解

    Django框架 之 querySet详解 浏览目录 可切片 可迭代 惰性查询 缓存机制 exists()与iterator()方法 QuerySet 可切片 使用Python 的切片语法来限制查询集 ...

  4. Linux命令累积

    常用命令 ipconfig  -查看本机ip.接口等信息 ping ip   -ping远程服务器或终端 cd ~      -返回根目录 cd .. 返回上级目录 cd ../..  返回上两级目录 ...

  5. 第04章-面向切面的Spring

    1. 什么是面向切面编程 AOP是什么 切面帮助我们模块化横切关注点. 横切关注点可被描述为影响应用[多处的]功能.如安全,应用许多方法会涉及安全规则. 继承与委托是最常见的实现重用 通用功能 的面向 ...

  6. Android绘图之Matrix

    一.概述 1. 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类.Android中的Matrix是一个3 x 3的矩阵,其内容如下 2.Matrix的对图像的处理 ...

  7. 并发编程学习笔记之Java存储模型(十三)

    概述 Java存储模型(JMM),安全发布.规约,同步策略等等的安全性得益于JMM,在你理解了为什么这些机制会如此工作后,可以更容易有效地使用它们. 1. 什么是存储模型,要它何用. 如果缺少同步,就 ...

  8. Java集合类总结 (二)

    LinkedList类 由于基于数组的链表有一个大的缺点,那就是从链表中间移除一个元素时需要将此元素后面的所有元素向前移动,会产生大量的开销,同样的在链表中间插入一个新元素也会有大量开销.如下图: L ...

  9. TSQL--SET ANSI_NULLS OFF

    当ANSI_NULLS 为ON时,遵循SQL92的标准,只能使用IS NULL 来判断值是否为NULL, 而不能使用=或<>来与NULL做比较,任何值包括NULL值与NULL值做=或< ...

  10. 如何让win32对话框居中显示

    在编写win32对话框程序,如果是用visual studio建的win32对话框程序,默认是不居中显示的,about 对话框也是这样的,用资源编辑器打开,可以在属性里面设置居中center 为tur ...