读书笔记_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关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...
随机推荐
- Django-模板语言和过滤器
Django模板语言 首先模板只是一个文本文件,它可以生成任何基于文本的格式(HTML.XML.CSS等),模板中包含变量,在模板被渲染的时候替换为最终的值,以及控制模板逻辑的标签. 变量使用{{ 变 ...
- 005_MAC下的VMware fushion快捷键(折中)
由于MAC和VMware Fushion虚拟机之间有一些快捷键的映射,所以Windows虚拟机就找了一个折中的方案.现总结MAC下的win常用快捷键==> <1>最小化窗口(Alt ...
- centos6.9系统优化
仅供参考 有道云笔记链接->
- cout如何输出十六进制
http://blog.csdn.net/okadler0518/article/details/4962340 cout<<hex<<i<<endl; //输出十 ...
- 十二、springcloud之展示追踪数据 Sleuth+zipkin
一.Zipkin简介 Zipkin是Twitter的一个开源项目,它基于Google Dapper实现.我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们 ...
- 五、springboot单元测试
1.为什么要写测试用例 1. 可以避免测试点的遗漏,为了更好的进行测试,可以提高测试效率 2. 可以自动测试,可以在项目打包前进行测试校验 3. 可以及时发现因为修改代码导致新的问题的出现,并及时解决 ...
- 20165333 2016-2017-2 《Java程序设计》第1周学习总结
20165333 2016-2017-2 <Java程序设计>第1周学习总结 教材学习内容总结 java 的地位 Java 的特点 安装JDK 系统环境的设置 Java程序的编写,编译和运 ...
- spring-boot分环境打包为tar包
1.pom配置 <!-- 多环境打包 start --> <profiles> <!-- 开发环境配置 --> <profile> <id> ...
- CF293B. Distinct Paths
B. Distinct Paths time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...
- 使用spring-boot-maven-plugin插件打包spring boot项目
在spring-boot项目中使用spring-boot-maven-plugin插件进行打包,输出可执行JAR包.项目包含多个模块,当打完包后在本地的maven仓库中发现输出的可执行JAR非常小,并 ...