读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第三部分)
下面来谈谈书中的第二部分,用Interface Classes来降低编译的依赖。从上面也可以看出,避免重编的诀窍就是保持头文件(接口)不变化,而保持接口不变化的诀窍就是不在里面声明编译器需要知道大小的变量,Handler Classes的处理就是把变量换成变量的地址(指针),头文件只有class xxx的声明,而在cpp里面才包含xxx的头文件。Interface Classes则是利用继承关系和多态的特性,在父类里面只包含成员方法(成员函数),而没有成员变量,像这样:
// Person.h
#include <string>
using namespace std; class MyAddress;
class MyDate;
class RealPerson; class Person
{
public:
virtual string GetName() const = ;
virtual string GetBirthday() const = ;
virtual string GetAddress() const = ;
virtual ~Person(){}
};
而这些方法的实现放在其子类中,像这样:
// RealPerson.h
#include "Person.h"
#include "MyAddress.h"
#include "MyDate.h" class RealPerson: public Person
{
private:
string Name;
MyAddress Address;
MyDate Birthday;
public:
RealPerson(string name, const MyAddress& addr, const MyDate& date):Name(name), Address(addr), Birthday(date){}
virtual string GetName() const;
virtual string GetAddress() const;
virtual string GetBirthday() const;
};
在RealPerson.cpp里面去实现GetName()等方法。从这里我们可以看到,只有子类里面才有成员变量,也就是说,如果Address的头文件变化了,那么子类一定会重编,所有用到子类头文件的文件也要重编,所以为了防止重编,应该尽量少用子类的对象。利用多态特性,我们可以使用父类的指针,像这样Person* p = new RealPerson(xxx),然后p->GetName()实际上是调用了子类的GetName()方法。
但这样还有一个问题,就是new RealPerson()这句话一写,就需要RealPerson的构造函数,那么RealPerson的头文件就要暴露了,这样可不行。还是只能用Person的方法,所以我们在Person.h里面加上这个方法:
// Person.h
static Person* CreatePerson(string name, const MyAddress& addr, const MyDate& date);
注意这个方法是静态的(没有虚特性),它被父类和所有子类共有,可以在子类中去实现它:
// RealPerson.cpp
#include “Person.h”
Person* Person::CreatePerson(string name, const MyAddress& addr, const MyDate& date)
{
return new RealPerson(name, addr, date);
}
这样在客户端代码里面,可以这样写:
// Main.h
class MyAddress;
class MyDate;
void ProcessPerson(const string& name, const MyAddress& addr, const MyDate& date);
// Main.cpp
#include "Person.h"
#include “MyAddress.h”;
#include “MyDate.h”; void ProcessPerson(const string& name, const MyAddress& addr, const MyDate& date)
{
Person* p = Person::CreatePerson(name, addr, date);
…
}
就可以减少编译依赖了。
总结一下,Handler classes与Interface classes解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性。减少编译依存性的关键在于保持.h文件不变化,具体地说,是保持被大量使用的类的.h文件不变化,这里谈到了两个方法:Handler classes与Interface classes。
Handler classes化类的成员变量为指针,在.h文件里面只包含class xxx的外来类声明,而不包含其头文件,在.cpp涉及到具体外来类的使用时,才包含xxx.h的头文件,这样最多只影响本身类的cpp重编,但因为.h文件没有变化,所以此类的对象存在的文件不必重编。
当然,书上说的Handler classes更想让我们在类A的基础上另造一个中间类AImp(成员函数完全与类A一致),这个中间类的成员中里面放置了所有类A需要的外来类的对象,然后类的逻辑细节完全在Almp.cpp中实现,而在A.cpp里面只是去调用Almp.cpp的同名方法。A.h的成员变量只有Almp的指针,这看上去好像一个Handler,因此而得名。
Interface classes则是将细节放在子类中,父类只是包含虚方法和一个静态的Create函数声明,子类将虚方法实现,并实现Create接口。利用多态特性,在客户端只需要使用到Person的引用或者指针,就可以访问到子类的方法。由于父类的头文件里面不包含任何成员变量,所以不会导致重编(其实由于父类是虚基类,不能构造其对象,所以也不用担心由于父类头文件变化导致的重编问题)。
请记住:
1. 支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式,基于此构想的两个手段是Handler classes和Interface classes。
2. 程序库头文件应该以“完全且仅有声明式”的形式存在,这种做法不论是否涉及templates都适用。
读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第三部分)的更多相关文章
- Effective C++ -----条款31:将文件间的编译依存关系降至最低
支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式.基于此构想的两个手段是Handle classes 和 Interface classes. 程序库头文件应该以“完全且仅有声明式 ...
- 条款31:将文件间的编译依存关系降至最低(Minimize compilation dependencies between files)
NOTE1: 1.支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式.基于此构想的两个手段是Handle classes 和 Interface classes. 2.程序库头文件应 ...
- 读书笔记_Effective_C++_条款四十一:了解隐式接口和编译期多态
从本条款开始,就进入了全书的第七部分:模板与泛型编程.模板与泛型在C++中是非常重要的部分,还记得本书第一章时,把C++视为一个联邦,它由四个州政府组成,其中一个政府就是模板与泛型了. 本条款是一个介 ...
- 读书笔记_Effective_C++_条款二十一:当必须返回对象时,别妄想返回其reference
在栈空间的临时成员变量在函数生命期结束后无法传出 friend A& operator*(const A& a, const A& b) { A temp; temp.data ...
- 读书笔记_Effective_C++_条款三十:了解inline的里里外外
学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...
- 读书笔记_Effective_C++_条款二十八:避免返回handlers指向对象内部成分
举个例子: class Student { private: int ID; string name; public: string& GetName() { return name; } } ...
- 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承
private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...
- 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承
这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...
- 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系
这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...
随机推荐
- 数据结构之队列(Python 版)
数据结构之队列(Python 版) 队列的特点:先进先出(FIFO) 使用链表技术实现 使用单链表技术,在表首尾两端分别加入指针,就很容易实现队列类. 使用顺序表list实现 # 队列类的实现 cla ...
- WCF - Autofac IOC
/// <summary> /// IOC实例提供者,基于AutoFac /// /// </summary> public class IocInstanceProvider ...
- java基础46 IO流技术(输出字符流/缓冲输出字符流)
一.输出字符流 1.1.输出字符流体系 --------| Writer:输出字符流的基类(抽象类) ----------| FileWriter:向文件输出数据输出字符流(把程序中的数据写到硬盘中 ...
- 读书笔记--C陷阱与缺陷(三)
第三章 1. 指针与数组 书中强调C中数组注意的两点: 1) C语言只有一维数组,但是数组元素可以是任何类型对象,是另外一个数组时就产生了二维数组.数组大小是常数(但GCC实现了变长数组..) ...
- thinkphp5高亮当前页(仅针对个人项目记录,不做通用参考)
<div class="navbg"> <ul class="menu"> <li> <a href="/& ...
- JAVA复习笔记分布式篇:zookeeper
前言:终于到分布式篇,前面把JAVA的一些核心知识复习了一遍,也是一个JAVA程序员最基本要掌握的知识点,接下来分布式的知识点算是互联网行业的JAVA程序员必备的技能: 概念:ZooK ...
- 20165203 学习基础和C语言基础调查
一.技能学习经验及体会 对于课外技能来说,我对很多领域都略知一二,但涉足不深,例如体育领域.摄影领域.绘画领域.书法领域等等,我所能拿得出手的就是体育领域的乒乓球了.娄老师的作业题目让我的思绪又回到了 ...
- 关于ARM指令那些你必须知道的东西
1.32位ARM指令每一位都有其作用,具体如下: 低12为第二操作数, 12~15位为目的寄存器, 16~19位为第一操作数, 20~27就是操作码, 28~31就是条件域. 2.多寄存器load和s ...
- CentOS7.5之Sqoop1.4.7的安装使用
一 Sqoop简介 Apache Sqoop(TM) 于 2012 年 3 月孵化出来,现在是一个顶级的 Apache 项目.是一种旨在有效地在 Apache Hadoop 和诸如关系数据库等结构化数 ...
- 跟我一起学WPF(2):WPF控件基础
WPF控件简介 通过上一篇XAML语言的介绍,我们知道,XAML是一个树形结构,同样,WPF控件作为构成整个XAML树的一部分,也是一个树形结构.我们看一个简单的例子. <Button.Cont ...