您的位置:首页 > 编程语言 > C语言/C++

C++ 接口与实现分离技术---如何将文件间的编译关系降至最低

2009-06-03 13:16 627 查看
    要将文件间的编译关系降到最低,就是要以“声明的依存性”替换“定义的依存性”。什么是“声明的依存性”和“定义的依存性”呢,简单的说是这样:

 
比如 //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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐