荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Dreamer (我与萤火虫), 信区: SoftDev
标  题: 【C++】[FAQ]为何我编译一个程序要花那么多时间?
发信站: 荔园晨风BBS站 (Mon Jun  7 19:57:19 2004), 站内信件

Q: 为何我编译一个程序要花那么多时间?
A: 也许是你的编译器有点不太对头——它是不是年纪太大了,或者没有安装正确
?也可能你的电脑该进博物馆了……对于这样的问题我可真是爱莫能助了。
不过,也有可能原因在于你的程序——看看你的程序设计还能不能改进?编译器是
不是为了顺利产出正确的二进制码而不得不吃进成百个头文件、几万行的源代码?
原则上,只要对源码适当优化一下,编译缓慢的问题应该可以解决。如果症结在于
你的类库供应商,那么你大概除了“换一家类库供应商”外确实没什么可做的了;
但如果问题在于你自己的代码,那么完全可以通过重构(refactoring)来让你的
代码更为结构化,从而使源码一旦有更改时需重编译的代码量最小。这样的代码往
往是更好的设计:因为它的藕合程度较低,可维护性较佳。

我们来看一个OOP的经典例子:

class Shape {
public: // interface to users of 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;
// ...
};


上述代码展示的设计理念是:让用户通过Shape的公共界面来处理“各种形状”;
而Shape的保护成员提供了各继承类(比如Circle,Triangle)共同需要的功能。
也就是说:将各种形状(shapes)的公共因素划归到基类Shape中去。这种理念看
来很合理,不过我要提请你注意:
要确认“哪些功能会被所有的继承类用到,而应在基类中实作”可不是件简单的事
。所以,基类的保护成员或许会随着要求的变化而变化,其频度远高于公共界面之
可能变化。例如,尽管我们把“center”作为所有形状的一个属性(从而在基类中
声明)似乎是天经地义的,但因此而要在基类中时时维护三角形的中心坐标是很麻
烦的,还不如只在需要时才计算——这样可以减少开销。
和抽象的公共界面不同,保护成员可能会依赖实作细节,而这是Shape类的使用者
所不愿见到的。例如,绝大部分使用Shape的代码应该逻辑上和color无关;但只要
color的声明在Shape类中出现了,就往往会导致编译器将定义了“该操作系统中颜
色表示”的头文件读入、展开、编译。这都需要时间!
当基类中保护成员(比如前面说的center,color)的实作有所变化,那么所有使
用了Shape类的代码都需要重新编译——哪怕这些代码中只有很少是真正要用到基
类中的那个“语义变化了的保护成员”。
所以,在基类中放一些“对于继承类之实作有帮助”的功能或许是出于好意,但实
则是麻烦的源泉。用户的要求是多变的,所以实作代码也是多变的。将多变的代码
放在许多继承类都要用到的基类之中,那么变化可就不是局部的了,这会造成全局
影响的!具体而言就是:基类所倚赖的一个头文件变动了,那么所有继承类所在的
文件都需重新编译。

这样分析过后,解决之道就显而易见了:仅仅把基类用作为抽象的公共界面,而将
“对继承类有用”的实作功能移出。

class Shape {
public: // interface to users of Shapes
virtual void draw() const = 0;
virtual void rotate(int degrees) = 0;
virtual Point center() const = 0;
// ...

// no data
};

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: // interface to users of 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;
};


[译注:这里作者的思路就是孤立变化,减少耦合。从这个例子中读者可以学到一
点Refactoring的入门知识 :O) ]




--
如果你真的爱萤火虫,你就不应该将她困在瓶子里;
如果你真的爱萤火虫,你应该放开她,让她在天空中自由自在地飞!
虽然你会不舍得她,但是最终你就会明白这样你才真正拥有了她!

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.223]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店