引言:编译时间成本

在项目中我们都会碰到修改既存类的情况:某个class实现文件做了些轻微改变,修改的不是接口,而是实现,而且只改private成分。

重新build这个程序,并预计只花数秒就好,当按下“Build”,结果整个世界都被重新编译和链接了!

问题是在c++并没有把“将接口从实现中分离”做得很好。class 的定义式不只详细叙述了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; //实现细节
};

在这个类上方,应该还存在着:

 #include <string>
#include "date.h"
#include "address.h"

头文件。

这样一来,便在Person定义文件和其含入文件之间形成了一种编译依存关系(compilation dependency)。如果这些头文件中有任何一个被改变,或这些文件所依赖的其他头文件有任何改变,那么每个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。这样的的连串编译依存关系(cascading compilation dependencies)会对许多项目造成难以形容的灾难。

第一节 实现细节和声明分开

为什么c++坚持将class的实现细目置于class定义式中?为什么不这样定义Person,将实现细目分开叙述:

 namespace std { class string;} //前置声明(不正确)
class Date; //前置声明
class Address; //前置声明
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;
...
};

如果这样,Person的客户就只有在Person接口被修改时才重新编译。

但这样有两个问题:

第一,string不是个class,它是个typedef。因此string前置声明并不正确,而且你本来就不应该尝试手工声明一部分标准程序库。你应该仅仅使用适当的#includes完成目的。标准头文件不太可能成为编译瓶颈。

第二,编译器必须在编译期间知道对象的大小,考虑这个:

 int main()
{
int x; // 定义一个int
Person p(params); // 定义一个Person
}

编译器看到X的时候,它都知道一个int有多大。但是当它看到p的时候,知道必须分配足够空间放置一个Person,但是他必须知道一个Person对象多大,获得这一信息的唯一办法是询问class定义式。然而,如果class定义式可以合法的不列出实现细目,编译器如何知道该分配多少空间?

针对这个问题,smalltalk,java实现了一个类似下面代码的逻辑:

 int main()
{
int x; // 定义一个int
Person p(params); // 定义一个指针指向Person
}

这样的功能在C++被叫做PIMPL,即:

 #include <string>
#include <memory>
class PersonImpl;
class Date;
class Address;
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::tr1::shared_ptr<PersonImpl> pImpl; //指向实现物的指针
};

这样,Person的客户就完全与Date,Address以及Person的实现细目分离了。那些classes的任何实现修改都不需要Person客户端重新编译。

这个分离的关键在于以“声明的依存性”替换“定义的依存性”,那正是编译依存性最小化的本质:让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。其他每件事都源自于这个简单的涉及策略:

如果用object reference 或 object pointer可以完成任务,就不要用objects。可以只靠声明式定义出指向该类型的pointer和reference;但如果定义某类型的objects,就需要用到该类型的定义式。

如果能够,尽量以class声明式替换class定义式。当你声明一个函数而它用到某个class时,你并不需要该class的定义式,纵使函数以by value方式传递该类型的参数(或返回值)亦然:

 class Date; //class 声明式
Date today();
void clearAppiontments(Date d);

为声明式和定义式提供不同的头文件。

这种使用pimpl idiom的classes,往往被称为Handle classes。

这种classes的办法之一就是将他们的所有函数转交给相应的实现类(implementation classes)并由后者完成实际工作。

 #include "Person.h"
#include "PersonImpl.h"
Person::Person(const std::string& name, const Date& birthday, const Address& addr)
: pImpl(new PersonImpl(name, birthday, addr))
{}
std::string Person::name() const
{
return pImpl->name();
}

另一个制作Handle class的办法是,令Person称为一种特殊的abstract base class(抽象基类)称为Interface classes。这种class的目的是详细一一描述derived classes的接口,因此它通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数,又来叙述整个接口。

一个针对Person而写的Interface class或许看起来像这样:

 class Person{
public:
virtual ~Person();
virtual std::string name() const = ;
virtual std::string birthday() const = ;
virtual std::string address() const = ;
...
};

◆总结

1.支持“编译依存最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。

2.程序库头文件应该以“安全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。

[Effective C++ --031]将文件间的编译依存关系降至最低的更多相关文章

  1. 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第三部分)

    下面来谈谈书中的第二部分,用Interface Classes来降低编译的依赖.从上面也可以看出,避免重编的诀窍就是保持头文件(接口)不变化,而保持接口不变化的诀窍就是不在里面声明编译器需要知道大小的 ...

  2. Effective C++ -----条款31:将文件间的编译依存关系降至最低

    支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式.基于此构想的两个手段是Handle classes 和 Interface classes. 程序库头文件应该以“完全且仅有声明式 ...

  3. 条款31:将文件间的编译依存关系降至最低(Minimize compilation dependencies between files)

    NOTE1: 1.支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式.基于此构想的两个手段是Handle classes 和 Interface classes. 2.程序库头文件应 ...

  4. [EffectiveC++]item31:将文件间的编译依存关系降至最低

    P143:“声明的依赖性"替换“定义的依存性”

  5. effective c++:inline函数,文件间编译依存关系

    inline函数 inline函数可以不受函数调用所带来的额外开销,编译器也会优化这些不含函数调用的代码,但是我们不能滥用Inline函数,如果程序中的每个函数都替换为inline函数那么生成的目标文 ...

  6. Effective C++ 笔记:条款 31 将编译关系降至最低

    31 : Minimize compilation dependencies between files 1 这关乎C++的类(或说都是类惹的祸) 1.1 C++类定义式的问题 C++类定义式不只叙述 ...

  7. 读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低

    1. 牵一发而动全身 现在开始进入你的C++程序,你对你的类实现做了一个很小的改动.注意,不是接口,只是实现:一个私有的stuff.然后你需要rebuild你的程序,计算着这个build应该几秒钟就足 ...

  8. 使用.bat 文件,批量编译项目文件。

    使用.bat 文件,批量编译项目文件. 2008-6-1来源:www.aspcool.com 作者:PCJIM 点击:次   path %path%;D:\Program Files\Microsof ...

  9. c++中,保证头文件只被编译一次,避免多重包含的方法

    保证头文件只被编译一次 #pragma once这是一个比较常用的C/C++杂注,只要在头文件的最开始加入这条杂注,就能够保证头文件只被编译一次. #pragma once是编译器相关的,有的编译器支 ...

随机推荐

  1. Linux性能监控之Memory篇

    首先说说虚拟内存和物理内存: 虚拟内存就是采用硬盘来对物理内存进行扩展,将暂时不用的内存页写到硬盘上而腾出更多的物理内存让有需要的进程来用.当这些内存页需要用的时候在从硬盘读回内存.这一切对于用户来说 ...

  2. 面向切面编程(AOP)及其作用

    在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用. 1.面向切面编程(AOP) 面向切面编程(AOP)就是对软件系统不同关注点的分离,开发者通过拦截方法调用并在方法调用前后添加辅助代码. ...

  3. 机器学习真的可以起作用吗?(2)(以二维PLA算法为例)

    一个问题:大多数情况下,M(hypothesis set的大小)是无穷大的,例如PLA算法.那么是不是我们的原则1就不能使用了? 我们试着做一些努力: Step1:寻找hypothesis set的e ...

  4. mybatis系列-06-输入映射

    通过parameterType指定输入参数的类型,类型可以是简单类型.hashmap.pojo的包装类型 6.1     传递pojo的包装对象 6.1.1     需求 完成用户信息的综合查询,需要 ...

  5. Hadoop在百度的应用

    百度作为全球最大的中文搜索引擎公司,提供基于搜索引擎的各种产品,包括以网络搜索为主的功能性搜索:以贴吧为主的社区搜索:针对区域.行业的垂直搜索.MP3音乐搜索,以及百科等,几乎覆盖了中文网络世界中所有 ...

  6. Calculating a bearing between points in location-aware apps

    https://software.intel.com/en-us/blogs/2012/11/30/calculating-a-bearing-between-points-in-location-a ...

  7. TQMsgPack序列还原clientdataset.data

    序列 procedure TForm1.Button2Click(Sender: TObject);var msgpack: TQMsgPack;begin msgpack := TQMsgPack. ...

  8. UIPanGestureRecognizer

    http://blog.csdn.net/huifeidexin_1/article/details/8282035 UIGestureRecognizer是一个定义基本手势的抽象类,具体什么手势,在 ...

  9. HDU 3668 Volume (数学,积分)

    题意:求图中交叉圆柱体的体积! 析:大体思路很明确,把两个圆柱的体积加起来 减去中间公共部分的即可!第一步首先得想到公共部分是一个怎样的图形.这个在高数下册例题中有讲到! 中间实线部分就是第一卦限内的 ...

  10. Boost的Serialization和SmartPoint搭配使用

    准确来说,这篇博文并不是译文,而是一篇某个网页中代码改写而来.原文章中的代码存在几处严重错误,网页又不提供留言功能(不是没有而是一个没有留言功能的留言板).4年过去了,作者对这些错误不更正让人无法接受 ...