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
#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-友元成员函数的更多相关文章
- C++ 友元 (全局函数做友元) (类做友元) (成员函数做友元)
1 //友元 全局函数做友元 2 /* 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 ...
- 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数
1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...
- 重载运算符:类成员函数or友元函数
类成员函数: bool operator ==(const point &a)const { return x==a.x; } 友元函数: friend bool operator ==(co ...
- C++学习之路—运算符重载(二)运算符重载作为类的成员函数和友元函数
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 对运算符重载的函数有两种处理方式:(1)把运算符 ...
- 友元(友元函数、友元类和友元成员函数) C++
有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的.例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员. 友元(frend)机制允许一个类将 ...
- C++运算符重载三种形式(成员函数,友元函数,普通函数)详解
首先,介绍三种重载方式: //作为成员函数重载(常见) class Person{ Private: string name; int age; public: Person(const char* ...
- C++运算符重载形式——成员函数or友元函数
运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...
- C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数
C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...
- c++友元函数、友元类、友成员函数
友元函数:不是类成员函数,是一个类外的函数,但是可以访问类所有成员. class Point{ public: friend void fun(Point t);//友元函数 private: int ...
随机推荐
- python小程序:备份文件
设计程序,有以下步骤: 需要备份的文件和目录由一个列表指定. 备份应该保存在主备份目录中. 文件备份成一个zip文件. zip存档的名称是当前的日期和时间. 解决方案: 版本一: #!/usr/bin ...
- uva1619
分析:这个题的关键是要找到,当某个值是最小值时它最大的影响区间时什么.可以通过单调队列(单调栈)在nlogn的时间内实现 #include <cstdio> #include <cs ...
- SpringBoot RestController 同时支持返回xml和json格式数据
@RestController 默认支持返回json格式数据,即使不做任何配置也能返回json数据 当接口需要支持xml或json两种格式数据时应该怎么做呢? 只要引入 Jackson xml的 ma ...
- HandleErrorAttribute
前言 一直在给Team的人强调“Good programming is good Error Handling”,没人喜欢YSOD(Yellow Screen of Death).我每次看到黄页的时候 ...
- AntD01 Angular2整合AntD、Angular2整合Material、利用Angular2,AntD,Material联合打造后台管理系统 ???
待更新... 2018-5-21 13:53:52 1 环境说明 2 搭建Angular项目 详情参见 -> 点击前往 辅助技能 -> 点击前往 3 创建共享模块 ng g m share ...
- tensorflow 中 feed的用法
Feed 上述示例在计算图中引入了 tensor, 以常量或变量的形式存储. TensorFlow 还提供了 feed 机制, 该机制 可以临时替代图中的任意操作中的 tensor 可以对图中任何操作 ...
- 权限管理RBAC
四张表: 1.module:id/name //模块 2.action:id /module_id/name //权限 3.user:id/name //用户表 4.group:id/user_id ...
- 智能IC卡中的文件系统
1.文件系统是COS的重要模块之一,它负责组织.管理.维护IC卡内存储的所有数据. 文件系统的设计和实现既是COS系统中最灵活.最有个性的部分,也是对系统整体结构影响最大的模块之一. 2.在IC卡内, ...
- [转]FreeMarker使用
copy自http://demojava.iteye.com/blog/800204 以下内容全部是网上收集: FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主 ...
- JAVA8 Lambda 表达式使用心得
List<HashMap> 指定数据求和: List<HashMap> kk = new ArrayList<>(); Map mmm = new H ...