31 将文件的编译依存关系降至最低

一、class定义式中包含实现细目

class Person{
public:
    Person(const std::string& name,const Date&birthday, const Address& addr);
    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    ...
private:
    //实现细目
    std::string theName;
    Date theBirthDate;
    Address theAddress;
};
int main(){
   int x;
   int Person(params);//编译器编译到此时会对Person分配内存,因此需要知道Person的大小(静态分配内存)
}

当Class的定义式不只详细叙述了class接口,还包括实现细目时,轻微的Class改动都会导致实现细目中相关的依赖重新编译。上述Person的定义中包含了string、Date、Address实现细节。编译时需要对Person文件中的成员变量分配内存,编译器要知道分配多大内存,就需要去查找他们的定义式,因此需要包含成员变量所属类型的定义式,而其定义式中又可能包含其他类型的成员变量,又需要包含其他类型的定义式…如此这样,一个编译单元可能包含了很多的文件,因此这个编译单元有任何改动这些依赖的文件都需要重新编译

二、如何分离接口和实现

包含class定义式会递归式的增强文件之间的编译依赖关系,下边的两种方式介绍了如何降低文件之间编译的依赖关系:pImp和接口。

2.1 pImpl idiom(pimpl是“pointer同implementation”)

  • 设计策略
    • 如果使用object references 或object pointers可以完成任务,就不要使用objects。
      可以只靠一个类型声明式就定义出指向该类型的referencespointers;但如果定义某类型的object,就需要用到该类型的定义式
    • 如果能够,尽量以class声明式替换class定义式
      当你声明一个函数而用到某个class时,你并不需要该class的定义;纵使函数以by value方式传递该类型的参数(或返回值)。
  • 为声明式和定义式提供不同的头文件
    需要两个头文件,一个用于声明式,一个用于定义式。这两个文件必须保持一致性。程序客户应该总是#include一个声明文件而非前置声明若干函数。程序库坐着也应该提供这两个头文件。如:<iosfwd>内涵iostream各组件的声明式,其对应定义则分布在若干不同的头文件内,包括<sstream>,<streambuf>,<fstream>,<iostream>。
  • C++提供关键字export,允许将template声明式和template定义式分割于不同的文件内。但是支持这个关键字的编译器目前非常少。

像Person这样使用pimpl idiom的class,被称为Handle class。另一种制作Handle Class的办法是abstract base class(抽象基类),称为Interface class。见2.2。

2.2 接口

  • 为什么接口路能降低文件间的编译依存关系?
    接口的目的是详细一一描述derived classes的接口,因此他们通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数,用来叙述整个接口。所以接口中不需要class的定义式,文件的依赖关系变低。
  • 接口如何创建对象?
    Interface class的客户通过特殊的函数来创建新对象。这样的函数通常称为factory函数或virtual构造函数(构造函数不能为虚函数,此处只是将这种特殊的函数称为virtual构造函数)。这样的函数往往被声明为static,且返回智能指针。

三、总结

  • 支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
  • 程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。
  • Handle class 的成本:
    • 成员函数必须通过implementation pointer取得对象数据,每一次访问都增加了一层间接性
    • 每一个对象消耗的内存数量必须增加一个implementation pionter的大小。
    • implementation pointer必须在Handle class的构造函数内初始化,导致动态分配内存带来的额外开销,以及遭遇bad_alloc异常的可能性。
  • Interface classes成本:
    • 每次virtual函数调用增加一次间接跳跃成本。
    • Interface classes派生的对象必须内含一个vptr。
  • 不论Handle classes 或Interface classes,一旦脱离了inline函数都无法有太大作为(?)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值