C++ 接口与实现分离技术---如何将文件间的编译关系降至最低
2009-06-03 13:16
627 查看
要将文件间的编译关系降到最低,就是要以“声明的依存性”替换“定义的依存性”。什么是“声明的依存性”和“定义的依存性”呢,简单的说是这样:
比如 //Person.h
Class Person 需要用到Class Address 和Class Date,于是我们在Person.h中#include "Date.h" 和#include“Address.h”. 这样实际上是产生了几个文件之间的编译依存关系。Person.h 依存Address.h和Date.h, 如果Date.h 和Address.h有任何修改,那么Person.h将需要改变,任何包含Person.h的文件也将会被卷入编译怪圈,甚至一直往上层递增。
如果Person.h只提供接口,不把实现细目提供给使用者,只要接口不变,我们理想的希望他的上层不会因为Person.h 需要的下层文件改变而卷入编译依赖的怪圈,那是否有实现这一目标的方法呢?那就是“声明的依存性”
如果我们这样写Person.h
只从Person.h来看,不会因为 CDate 或CAddress的变化或是具体实现细节的变化而引起Person.h的任何变化,那么任何包含Person.h的文件,都不会因为Person.h下层的文件改动而产生任何编译依赖了。但是这样做需要克服一个问题:
前置声明一件东西,需要在编译期间知道对象的大小
比如
int 型编译器是知道分配多少内存的,可是Person呢,唯一能获取的方法是询问Person class 的定义式,然而要想隐藏掉class的实现细目以达到上述效果,有什么办法呢?那就是“将对象实现细目隐藏于一个指针背后”;
具体到Person.h 我们可以将其分为两个class, 一个提供接口,一个负责实现接口(这里就是想要达到的接口与实现分离的关键)
如果public的这些接口能够实现功能,那么这个Person就可以说是真正的“接口与实现分离”!
这个分离的关键在于“声明的依赖性”替换“定义的依赖性”,那正是编译依赖性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与他文件内的声明式(而非定义式)相依。
这里可能有人会担心,当声明一个函数,返回值或是参数需要用到CDate或CAddress怎么办?而且甚至是pass-by-value
CDate today();
void canelAppointment(QDate d);
令人惊奇的是,依然没有问题,但是一旦有人要调用它,其定义式则必须曝光,而这些工作只需要在cpp文件中#include “Date.h”并且加以实现即可。
具体的Person实现如下:
这里实质是让Person变成了一个handle calss, 这并不会改变它做的事,而是改变它做事的方式。
另外一种制作handle calss的方式是用抽象基类 (interface class)实现,这里就不详述了,他是实现接口与实现分离的又一种方式,但与最小化文件编译依赖关系好像没有太大关系。而且我们可以发现,这两种方法实际上就是对应设计模式中的strategy和template模式。
其实总归下来,要实现编译依赖最小化,一定要保证:
1. 头文件中只用声明式,不给出实现细目,具体#include放在cpp中完成( 系统头文件可以直接包含,编译瓶颈通常不在这里。)
2. 实现细目(成员变量)尽量使用指针。
3. 参数尽量使用指针和引用
要达到接口与实现的分离:
1. 将class分成两个class, 一个用于接口,一个用于实现, 接口class持有实现class的指针
2. 接口class不含有任何实现细目,将具体实现细目丢入实现class完成
参考资料:《Effective C++ 3rd Edition》 条款31:将文件间的编译依存关系降至最低
http://blog.csdn.net/starlee/archive/2006/02/27/610825.aspx
比如 //Person.h
#ifndef PERSON #define PERSON #include "Date.h" #include "Address.h" class Person { public: Person(CAddress ad, CDate date); .... public: string address() const; string birthday() const; public: CAddress _add; //实现细目 CDate _date; //
Class Person 需要用到Class Address 和Class Date,于是我们在Person.h中#include "Date.h" 和#include“Address.h”. 这样实际上是产生了几个文件之间的编译依存关系。Person.h 依存Address.h和Date.h, 如果Date.h 和Address.h有任何修改,那么Person.h将需要改变,任何包含Person.h的文件也将会被卷入编译怪圈,甚至一直往上层递增。
如果Person.h只提供接口,不把实现细目提供给使用者,只要接口不变,我们理想的希望他的上层不会因为Person.h 需要的下层文件改变而卷入编译依赖的怪圈,那是否有实现这一目标的方法呢?那就是“声明的依存性”
如果我们这样写Person.h
#ifndef PERSON #define PERSON class CDate; //前置声明 class CAddress class Person { public: Person(CAddress ad, CDate date); .... public: string address() const; string birthday() const; ... }
只从Person.h来看,不会因为 CDate 或CAddress的变化或是具体实现细节的变化而引起Person.h的任何变化,那么任何包含Person.h的文件,都不会因为Person.h下层的文件改动而产生任何编译依赖了。但是这样做需要克服一个问题:
前置声明一件东西,需要在编译期间知道对象的大小
比如
int main() { int x; //定义一个int Person p(pararms) //定义一个Person }
int 型编译器是知道分配多少内存的,可是Person呢,唯一能获取的方法是询问Person class 的定义式,然而要想隐藏掉class的实现细目以达到上述效果,有什么办法呢?那就是“将对象实现细目隐藏于一个指针背后”;
int main() { int x; //定义一个int Person *p; //定义一个Person }
具体到Person.h 我们可以将其分为两个class, 一个提供接口,一个负责实现接口(这里就是想要达到的接口与实现分离的关键)
#ifndef PERSON #define PERSON class CDate; //前置声明 class CAddress; class PersonImpl; class Person { public: Person(CAddress ad, CDate date); .... public: string address() const; string birthday() const; ... private: PersonImpl *pImpl; //指向具体实现的类, pimpl (pointer to implementation) }
如果public的这些接口能够实现功能,那么这个Person就可以说是真正的“接口与实现分离”!
这个分离的关键在于“声明的依赖性”替换“定义的依赖性”,那正是编译依赖性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与他文件内的声明式(而非定义式)相依。
这里可能有人会担心,当声明一个函数,返回值或是参数需要用到CDate或CAddress怎么办?而且甚至是pass-by-value
CDate today();
void canelAppointment(QDate d);
令人惊奇的是,依然没有问题,但是一旦有人要调用它,其定义式则必须曝光,而这些工作只需要在cpp文件中#include “Date.h”并且加以实现即可。
具体的Person实现如下:
#include "Person.h" #include "PersonImpl.h" //PersonImpl和Person有着完全相同的成员函数,两者接口完全相同 Person::Person(const CAddress &ad, const CDate &date):pImpl(new PersonImpl(ad,date)) {} string Person::address() const { pImpl->address(); }
这里实质是让Person变成了一个handle calss, 这并不会改变它做的事,而是改变它做事的方式。
另外一种制作handle calss的方式是用抽象基类 (interface class)实现,这里就不详述了,他是实现接口与实现分离的又一种方式,但与最小化文件编译依赖关系好像没有太大关系。而且我们可以发现,这两种方法实际上就是对应设计模式中的strategy和template模式。
其实总归下来,要实现编译依赖最小化,一定要保证:
1. 头文件中只用声明式,不给出实现细目,具体#include放在cpp中完成( 系统头文件可以直接包含,编译瓶颈通常不在这里。)
2. 实现细目(成员变量)尽量使用指针。
3. 参数尽量使用指针和引用
要达到接口与实现的分离:
1. 将class分成两个class, 一个用于接口,一个用于实现, 接口class持有实现class的指针
2. 接口class不含有任何实现细目,将具体实现细目丢入实现class完成
参考资料:《Effective C++ 3rd Edition》 条款31:将文件间的编译依存关系降至最低
http://blog.csdn.net/starlee/archive/2006/02/27/610825.aspx
相关文章推荐
- C++进阶 降低文件间的编译依存关系(接口与实现解耦合)
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第一部分)
- Effectiv C++条款31 将文件间的编译依存关系降至最低 Handle Class和Interface Class完整实现
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第二部分)
- effective C++ 条款 31:将文件间的编译依存关系降至最低
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第三部分)
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低
- Effectiv C++条款31 将文件间的编译依存关系降至最低 Handle Class和Interface Class完整实现
- Effectiv C++条款31 将文件间的编译依存关系降至最低 Handle Class和Interface Class完整实现
- C++ 分文件写法(接口与实现分离)
- effective C ++ 学习笔记之 item 31 将文件间的编译依赖关系降至最低(未完成)
- Effective C++笔记_条款31将文件间的编译依存关系降至最低
- 再论C++中接口与实现分离的技术
- Effective C++ -----条款31:将文件间的编译依存关系降至最低
- 1.将文件间的编译依存关系降至最低