4.3.1 源文件分割
一个很出名的减少代码大小的技术就是:对于两个很大的函数,如果某些程序只需要其中的一个函数,而不需要另外一个函数,那么我们就应该把这两个函数分别放于不同的程序库实现文件里面。我们把这种技术称为源文件分割技术(智能编译系统没有使用这种技术的必要,因为它们本身就可以实现相似的技术,但现今几乎不存在这样的编译系统)。现在请考虑4.2.2节的BSTreebase类:
- class BSTreebase {
- public:
- int size() const;
- void thread();
- //...
- };
假设由于参数的原因,上面两个函数都很大(实际上,size函数通常都是很小的)。而且可能存在某些程序,它们只需要其中的一个成员函数,而并不需要另外一个成员函数,那么源文件分割技术将指导我们把这两个函数的定义分别放在不同的文件里面。另外,如果thread函数需要调用私有成员函数dothread,并且只有thread调用dothread函数,那么源文件分割技术也要求我们把dothread函数和thread函数放在同一个文件里面。
当我们考虑模板的时候,事情将变得更加复杂一点。假设4.2.2节的BSTree模板类并不是从类BSTreebase继承而来的:
- template<class T>
- class BSTree{
- public;
- int size() const;
- void thread();
- //...
- };
当我们编写BSTree类的模板成员函数的定义时,为了使用户的模板实例化程序可以存取这些函数,我们就必须把这些函数的定义放在公共头文件里面,而且,用户的模板实例化程序还需要进一步组织模板源代码(我们将在9.7节详细叙述用户的这种需求),因此,在这种情况下,我们就不能在这些函数定义的目标代码中进行源文件分割,因为我们根本就没有这个选择的权利。
如果对一个大的程序库强行应用源文件分割,那么可能会产生问题。首先,现有的许多用于创建和操纵目标代码的档案文件的程序不能处理包含几百个小文件的档案文件。为了解决这个问题,现有的许多C++(www.cppentry.com)程序库都提供几个比较小的档案文件,而不是只提供一个大的档案文件。然而,这个解决方案的使用要慎重一些,因为它要求(程序库)用户切记按照正确的顺利链接所有的档案文件。
其次,如果我们在程序库中创建了调试变量(见5.3.1节),那么许多库的目标文件将会包含许多重复的信息。如果程序员接着使用我们程序库的调试变量来创建某个程序的调试变量,那么程序的代码数量将会急剧增大,而这有背源文件切割的初衷。
为了解决上面这两个问题,我们可以放宽对源文件切割的要求;把那些可能会在同一个程序里使用的函数集合都放在同一个文件里面。但在这种情况下,不再能够保证仍然可以得到最小化的正确链接时间开销。