C++编译时间过长解决方案

对于一个中型或者以上项目,编译时间本来就不短,如果在编码过程中,一些问题不注意,将使编译时间更长,下面介绍几点需要注意的地方。

 

关于C++ coding Standards》以下几条整改原则:


关于include的原则最多,因为包含头文件相当于将代码复制到本文件来编译,而头文件又经常是用来被别人包含的,所以工程文件多了,每个文件都有include链(包含的文件又include了其他文件),该链条不会止步 于你工程,而会延伸到你所有使用的第3方库里面。

能够去掉的include就去掉

说明:1.代码编写过程中或多或少都有一些历史遗留的不必要的头文件包含在你的文件里面,找到他们并去掉之。

2.去掉include链里面重复的include

能够在cpp里面include的头文件不要在头文件里面include
      
说明:尽量去掉每个cpp会被串起来的头文件膨胀的机会。

 

把大多数模块都要使用的库文件或者稳定类的头文件include放到预编译头文件“stdafx.h”里面
说明:由于预编译头文件里面include的内容只会compile一次而被link多次,把一些常用类放到这里会降低很多编译时间,但也不能乱来,要点在 大多数稳定,如果一个头文件经常变化,他的一次小改动都会引起整个工程rebuild,哪怕只是一个注释,因为所有的cpp文件都包含了 stdafx.hstdafx.h又包含了这个容易变动的头文件。

 

想要具体了解可以参考这本书,这本书C++ coding Standards》(英文版)现在在我这里,另外可以参考《fective C++》关于编译的章节,这本书在邹鸣哪里。

 

    另外可以在继承关系,如果使用不当,也会产生编译时间过长的问题,下面这个帖子转自C++ FAQs关于《我的程序为什么编译时间过长》,讲解者是Bjarne Stroustrup

 

Bjarne Stroustrup博士,1950年出生于丹麦,先后毕业于丹麦阿鲁斯大学和英国剑挢大学,AT&T大规模程序设计研究部门负责人, AT&T 贝尔实验室和ACM成员。1979年,B. S开始开发一种语言,当时称为"C with Class",后来演化为C++1998年,ANSI/ISO C++标准建立,同年,B. S推出其经典著作The C++ Programming Language的第三版。

你的编译器可能有问题。也许它太老了,也许你安装它的时候出了错,也许你用的计算机已经是个古董。在诸如此类的问题上,我无法帮助你。

但是,这也是很可能的:你要编译的程序设计得非常糟糕,以至于编译器不得不检查数以百计的头文件和数万行代码。理论上来说,这是可以避免的。如果这是你 购买的库的设计问题,你对它无计可施(除了换一个更好的库),但你可以将你自己的代码组织得更好一些,以求得将修改代码后的重新编译工作降到最少。这样的 设计会更好,更有可维护性,因为它们展示了更好的概念上的分离。

看看这个典型的面向对象的程序例子:

class Shape {
public: //
使用Shapes的用户的接口
virtual void draw() const;
virtual void rotate(int degrees);
// ...
protected: // common data (for implementers of Shapes)
Point center;
Color col;
// ...
};

class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
// ...
protected:
int radius;
// ...
};

class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
// ...
protected:
Point a, b, c;
// ...
};


设计思想是,用户通过Shapepublic接口来操纵它们,而派生类(例如CircleTriangle)的实现部分则共享由protected成员表现的那部分实现(implementation)。

这不是一件容易的事情:确定哪些实现部分是对所有的派生类都有用的,并将之共享出来。因此,与public接口相比,protected成员往往要做多 得多的改动。举例来说,虽然理论上中心”(center)对所有的图形都是一个有效的概念,但当你要维护一个三角形的中心的时候,是一件非常麻烦的 事情——对于三角形,当且仅当它确实被需要的时候,计算这个中心才是有意义的。

protected
成员很可能要依赖于实现部分的细 节,而Shape的用户(译注:user此处译为用户,指使用Shape类的代码,下同)却不见得必须依赖它们。举例来说,很多(大多数?)使用 Shape的代码在逻辑上是与颜色无关的,但是由于Shape颜色这个定义的存在,却可能需要一堆复杂的头文件,来结合操作系统的颜色概念。

protected部分发生了改变时,使用Shape的代码必须重新编译——即使只有派生类的实现部分才能够访问protected成员。

于是,基类中的实现相关的信息”(information helpful to implementers)对用户来说变成了象接口一样敏感的东西,它的存在导致了实现部分的不稳定,用户代码的无谓的重编译(当实现部分发生改变时), 以及将头文件无节制地包含进用户代码中(因为实现相关的信息需要它们)。有时这被称为脆弱的基类问题”(brittle base class problem)

一个很明显的解决方案就是,忽略基类中那些象接口一样被使用的实现相关的信息。换句话说,使用接口,纯粹的接口。也就是说,用抽象基类的方式来表示接口:

class Shape {
public: //
使用Shapes的用户的接口
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
//
没有数据
};

class Circle : public Shape {
public:
void draw() const;
void rotate(int) { }
Point center() const { return center; }
// ...
protected:
Point cent;
Color col;
int radius;
// ...
};

class Triangle : public Shape {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Color col;
Point a, b, c;
// ...
};


现在,用户代码与派生类的实现部分的变化之间的关系被隔离了。我曾经见过这种技术使得编译的时间减少了几个数量级。

但是,如果确实存在着对所有派生类(或仅仅对某些派生类)都有用的公共信息时怎么办呢?可以简单把这些信息封装成类,然后从它派生出实现部分的类:

class Shape {
public: //
使用Shapes的用户的接口
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...
// no data
};

struct Common {
Color col;
// ...
};

class Circle : public Shape, protected Common {
public:
void draw() const;
void rotate(int) { }
Point center() const { return center; }
// ...
protected:
Point cent;
int radius;
};

class Triangle : public Shape, protected Common {
public:
void draw() const;
void rotate(int);
Point center() const;
// ...
protected:
Point a, b, c;
};

 

 
上一篇:浮躁的会议


下一篇:C++中命名空间"std"名字由来的思考