程序设计方式 之 嵌套头文件包含方式

       一直以来写程序有个习惯,喜欢把常用的STL类,或者其他常用的类,变量,宏等定义到一个文件内,然后在使用其中一个类的地方包含这个文件。一直再使用,也一直存在困惑,这种设计方式的能否放心大胆的使用,对它始终心存畏惧,所有这些促使我完成这篇文章,并且经过种种测试,有足够的信心继续使用这种设计方式。
       操作如下
定义base.h文件,包含基本的STL类,并且通过typedef建立不同的映射关系,以便减少不同的文件出现过多vector<string>之类的声明方式。当需要使用vector<string>类型时,只需要包含base.h文件即可。
 
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <algorithm>
#include “macros.h”
#include “functions.h”
 
using namespace std;
 
typedef pair<string,string> Upss;
typedef vector<string> Uvstr; typedef vector<string>::iterator Uvstror;
typedef vector<Upss> Uvpsstr; typedef vector<Upss>::iterator Uvpsstror;
typedef pair<string,Uvstr> Upsvstr; typedef pair<string,Uvpsstr> Upsvpsstr;
typedef map<string,Uvstr> Umsvstr; typedef map<string,Uvstr>::iterator Umsvstror; typedef map<string,Uvpsstr> Umsvpsstr; typedef map<string,Uvpsstr>::iterator Umsvpsstror;
typedef vector<Upsvstr> Uvpsvstr; typedef vector<Upsvstr>::iterator Uvpsvstror;
typedef vector<Upsvpsstr> Uvpsvpsstr; typedef vector<Upsvpsstr>::iterator Uvpsvpsstror;
 
       另外我还喜欢定义一个macros.h文件,在这个文件内定义常用的宏,比如

const string INTERFACE_VERSION="1.0";

#define DATA_BUFFER_SIZE 24576
#define KEYDB_USER "db_user"
#define KEYDB_PASSWORD "db_password"
 
 
       有时我需要的定义一些常见函数,还会定义一个functions.h类,比如定义
template<class T> inline const T& max(const T& lhs, const T& rhs)
{     return lhs>rhs?lhs:rhs;
}
 
而后我会在base.h中包括macros.h和functions.h文件。
 
通过以上文件的包含关系,在我需要STL类型,宏定义,常用函数的文件中,只是包含base.h文件即可,这样就可以肆无忌惮的使用STL类型,宏定义,常用函数了。
这样做至少有如下几个优点:
1:减少包含文件的数量,书写简单,不用包含大量头文件,还不影响使用。
2:调用简单,包含base.h文件后,可以方便使用STL的各种类型,宏定义,常用函数等。
3:小组开发时,便于统一规范编码方式,并且有力于把小组中共性编码提出来模块化,方便别人调用。
4:在小组开发中,熟悉base.h结构后,可以方便代码编写,阅读和维护。当看到Upss就知道它是pair<string,string>,可以有效避免每个人把pair<string,string>映射为不同类型,方便维护。
5:方便以后开发,一旦这种形式在小组内形成默契,再开发新项目时,可以把这种方法直接移植到新项目中,减少开发设计时间。
但是这种方法有个缺点,就是base.h文件要求的是大而全,而包含base.h的文件中,并不需要base.h中的所有的类型,方法和宏定义,这样无形包含base.h的文件中大量无用文件,增加编译时间,并且当base.h文件修改时,所有依赖base.h的文件都需要重新编译,也需要大量的重新编译时间。所以这种方法不适合大中型项目。就目前的情况来说,个人认为这种方法在10人以下,或者代码量在10万以下,或者在文件总数不超过100个的情况下,都可以考虑使用这种方式,以下是我将对这种形式做详细的对比说明,以给你足够的信心。
为了方便说明和记忆,我们把这种方式叫嵌套头文件包含方式。原因就是,当我们需要使用string之类的对象时,不包含string的类所在的头文件,而是把对string的头文件放到base.h文件内,通过包含base.h来嵌套包含string的头文件,好像增加了一层嵌套。
嵌套头文件包含方式的优点属于感性认识,认可上面列举的优点,意味着对这种方式的认可,至少在小项目中,我认为这种方式还是很有优势的,并且我在小项目中一直也这么做的,虽然这种方式会增加编译时间,增加编译对象大小(?)。
嵌套头文件包含方式的缺点是可以理性测试的,下面我将逐渐测试,以便在你使用这种方式时,明确地理解优缺点,根据项目需求,权衡利弊是否使用这种方式。这种方式是一把双刃剑,在你认可这种方式的优点时,我也要告诉你,它的缺点,真实存在的缺点。
我定义了base.h,macros.h,testfile1.cc,testfile2.cc,testfile3.cc,functions.h,main.cc    ,testfile1.h,testfile2.h,testfile3.h等10个文件,他们作用如下
Base.h: 嵌套包含文件;
Macros.h: 定义常用的宏;
Functions.h: 定义常用函数;          Testfile[1-3].[h,cc]: 模拟3个文件,分别采用嵌套文件包含方式和普通的文件包含方式。
Main.cc: 从少到多依次调用testfile[1-3].h,从而查找嵌套文件包含方式和普通文件包含方式,随引用文件数量增多在编译,生成对象方面的之间的差异。
在以下测试中,我们使用最常见的 g++配置,不做任何特殊的设置。
预处理文件变化 ,预处理我们使用g++ -C –E方式,嵌套文件方式在0,1,2,3个引用文件情况下都为2.1M,普通文件方式为1.8M,嵌套头文件包含方式产生的预处理文件不会随应用嵌套文件的增多而线形增大,大小一直不便。
 
通过下表我们可以看到两者生成的对象,大小没有变化(两者还是有变化的,只是大小在 K字节的范围内,所以从下面的例子看不出来)
 
Main.o
Testfile1.o
Testfile2.o
Testfile3.o
普通方式
29k
25k
25k
25k
嵌套方式
29k
25k
25k
25k
   
我们察看一下生成对象的时间(增加编译选项 -DUSE_NESTING_METHOD为使用嵌套方式测试,以下类同)

time g++ -c testfile1.cc testfile2.cc testfile3.cc main.cc real    0m3.049s user    0m2.344s sys     0m0.700s

time g++ -c testfile1.cc testfile2.cc main.cc real    0m2.288s user    0m1.764s sys     0m0.516s

time g++ -c testfile1.cc main.cc real    0m1.533s user    0m1.288s sys     0m0.236s

time g++ -c main.cc
real     0m0.778s
user     0m0.636s
sys      0m0.132s
  time g++ -c testfile1.cc testfile2.cc testfile3.cc main.cc -DUSE_NESTING_METHOD
real     0m3.371s
user     0m2.528s
sys      0m0.840s

time g++ -c testfile1.cc testfile2.cc main.cc -DUSE_NESTING_METHOD real    0m2.536s user    0m2.020s sys     0m0.504s

time g++ -c testfile1.cc main.cc -DUSE_NESTING_METHOD real    0m1.698s user    0m1.308s sys     0m0.388s

time g++ -c main.cc -DUSE_NESTING_METHOD real    0m0.857s user    0m0.704s sys     0m0.148s

 
通过以上对比可以发现使用嵌套方式时,每个文件编译时间比普通方式多用 0.08s的时间。
 
接下来我们察看生成可执行文件的情况
普通方式所用时间
命令
时间

time g++ -o nomain main.cc

Real 0m0.648s
User 0m0.492s
Sys 0m0.148s

time g++ -o nomain main.cc testfile1.cc

Real 0m1.612s
User 0m1.268s
Sys 0m0.344s

time g++ -o nomain main.cc testfile1.cc testfile2.cc

Real 0m2.393s
User 0m1.896s
Sys 0m0.496s

time g++ -o nomain main.cc testfile1.cc testfile2.cc testfile3.cc

Real 0m3.187s
User 0m2.584s
Sys 0m0.564s
 
嵌套方式所用时间
命令
时间

time g++ -o nomain main.cc -DUSE_NESTING_METHOD

Real 0m0.755s
User 0m0.588s
Sys 0m0.156s

time g++ -o nomain main.cc testfile1.cc -DUSE_NESTING_METHOD

Real 0m1.767s
User 0m1.312s
Sys 0m0.452s

time g++ -o nomain main.cc testfile1.cc testfile2.cc -DUSE_NESTING_METHOD

Real 0m2.632s
User 0m2.212s
Sys 0m0.416s

time g++ -o nomain main.cc testfile1.cc testfile2.cc testfile3.cc -DUSE_NESTING_METHOD

Real 0m3.505s
User 0m2.688s
Sys 0m0.812s
 
普通方式可执行文件大小
命令
大小(字节)

g++ -o nomain main.cc

7039

g++ -o nomain main.cc testfile1.cc

29949

g++ -o nomain main.cc testfile1.cc testfile2.cc

31603

g++ -o nomain main.cc testfile1.cc testfile2.cc testfile3.cc

33721
 
嵌套方式可执行文件大小
命令
时间

g++ -o nomain main.cc -DUSE_NESTING_METHOD

7039

g++ -o nomain main.cc testfile1.cc -DUSE_NESTING_METHOD

30061

g++ -o nomain main.cc testfile1.cc testfile2.cc -DUSE_NESTING_METHOD

32291

g++ -o nomain main.cc testfile1.cc testfile2.cc testfile3.cc -DUSE_NESTING_METHOD

34769
 
通过以上的对比,我们可以清晰地知道嵌套方式在编译和生成对象两个环节比普通的包含方式要消耗更多的时间和资源(文件大小)。这个在我们的预料范围之内,但是令我们困惑的是,最后生成的可执行文件的大小不一致?其实这是正常现象,我们定义 base.h文件时,在引用的头文件包含时,已经更改了这个头文件的预编译结构,导致最后生成的可执行文件有差异,这也是为什么每次我们在更改头文件后,必须重新编译的原因。其实这种方式只是更改了可执行文件中的函数索引结构,对程序的运行速度没有什么影响,当然可执行文件运行时要比普通方式多消耗一些内存。以下是循环调用10000次后运行的时间,两者没有太大的差异。
 
普通包含文件方式
real     0m12.592s
user     0m1.796s
sys      0m2.872s
 
嵌套包含方式
real     0m12.151s
user     0m1.692s
sys      0m2.844s
 
这种嵌套头文件的包含方式有很多方便的应用情况,比如在 a.h,b.h,c.h,d,h分别声明了类A,B,C,D四个对象,为了方便调用,我们可以定义e.h,在e.h中
#include “a.h”
#include “b.h”
#include “c.h”
#include “d.h”

这样在需要即需要A有需要B的文件引用中直接#include “e.h”即可,对于调用者来说非常方便。也许你在开发过程中也一直使用这种开发方式,只是一直没有明确下来这样使用的缺点是否在可允许的范围内,或者一直懒的去做验证,或者一直就没有考虑这点小小的问题,通过本篇文章,希望给你一个明确的答复,在小项目中毫不犹豫的使用吧!

 
本着科学严谨的态度我们应该在不同类型和版本的编译器,做多种实现,无奈我们很难得到各种版本和类型编辑机,所以以上测试结果仅仅为了说明嵌套方式和普通方式的之间存在差异,而不能严格量化他们之间的差异,其目的就是告诉大家嵌套头文件方式,在编译和生成对象方法存在差异。以上例子只在 g++编译器下做了测试。没有在VC,或者其它编译环境下做测试,有条件可以测试一下,欢迎公布测试结果。
 
编译环境 g++ 3.4.4
 
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值