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

       一直以来写程序有个习惯,喜欢把常用的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"

 

#define VALID_CHARSET 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/.@#-,;

 

       有时我需要的定义一些常见函数,还会定义一个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

 

时间: 2024-08-31 10:01:11

程序设计方式 之 嵌套头文件包含方式的相关文章

浅析VC++中的头文件包含问题_C 语言

在一些大的工程中,可能会包含几十个基础类,免不了之间会互相引用(不满足继承关系,而是组合关系).也就是需要互相声明.好了,这时候会带来一些混乱.如果处理得不好,会搞得一团糟,根据我的经验,简单谈谈自已的处理办法: 编码时,我们一般会尽量避免include头文件,而是采用声明 class XXX.但有时候还是必须用Include头文件,那么,两者的划分在于什么呢? 应该是很明确的,但书上好像都少有提及. 首先:我们要明白为什么要用声明取代头文件包含:对了,是为了避免无必要的重编译(在头文件发生变更

xcode-vtk 头文件包含的相关问题

问题描述 vtk 头文件包含的相关问题 编译好的vtk中是否有include文件夹?我在调用vtk的头文件时,如下面代码所示: #include ""vtkDICOMImageReader.h""#include ""vtkPiecewiseFunction.h""#include ""vtkColorTransferFunction.h""#include ""vt

关于windows h-请教大神有什么书详细介绍像windows.h这类头文件包含的函数的具体用法的?

问题描述 请教大神有什么书详细介绍像windows.h这类头文件包含的函数的具体用法的? 请教大神有什么书详细介绍像windows.h这类头文件包含的函数的具体用法的? 我想系统的学一学这个头文件下的函数的具体用法,苦于找不到相关的书籍,求大神推荐啊 解决方案 WINDOWS核心编程 http://download.csdn.net/detail/huangxy10/5783907 Win32 API 参考手册 http://www.pudn.com/downloads118/ebook/det

c语言头文件包含问题-关于头文件包含的问题,请教大家!

问题描述 关于头文件包含的问题,请教大家! 程序源文件中有两个头文件,头文件A中定义了一个结构体类型_tag_addr,并在头文件开头使用了#ifnodef #define预编译宏;头文件B中定义了一个结构体类型_tag_bddr,也在头文件开头使用了预编译宏,现在的问题是我要在头文件A中声明一个_tag_bddr类型的变量,在头文件B中声明一个_tag_addr类型的变量,所以要在头文件A开头包含头文件B,也要在头文件B中包含头文件A,但是这样做编译时头文件A报错,说是没有_tag_bddr类

socket-【C++】符号重定义,头文件包含问题

问题描述 [C++]符号重定义,头文件包含问题 #pragma once #include "Common.h" #include "WinSock.h" class CSockOperation { public: int Send(SOCKET socket,const char* buf,int len);//失败返回错误值 int Recv(SOCKET socket,char* lpBuf,int nBufLen);//失败返回错误值 int CSockO

头文件 webkit-makefile 头文件包含问题

问题描述 makefile 头文件包含问题 我最近在看Android使用的Webkit库,发现webkit/WebCore/ForwardingHeaders/wtf/Forward.h文件中包含的头文件是#include <JavaScriptCore/Forward.h>,而JavaScriptCore路径下没有Forward.h,实际的路径是webkit/JavaScriptCore/wtf/Forward.h,我对Makefile不是特别熟悉,望高人指点!

[新人求助]头文件包含方面的问题

问题描述 个人认为是头文件包含那边的问题,但是在做窗体程序的时候出现的,便在这个区中询问.菜鸟一枚,请多指教.我想制作这样一个功能,建立了一个窗体后,在窗体上按一个按钮会新建另一个窗体同时hide本窗体:然后在新建的窗体上设一个返回按钮,点击后会把原先的窗体show回来.姑且把这两个窗体文件叫做form1.h和form2.h.1.我在form1.h中include了form2.h,然后执行form2的gcnew,没有问题:2.然后我在form2.h中include了form1.h.这下子在for

C++头文件的包含顺序研究

一.<Google C++ 编程风格指南>里的观点        公司在推行编码规范,领导提议基本上使用<Google C++ 编程风格指南>.其中<Google C++ 编程风格指南>对于头文件的包含顺序是这样的:   Names and Order of Includes link ▽Use standard order for readability and to avoid hidden dependencies:C library, C++ library,

c语言问题-C语言的头文件有包含顺序吗?

问题描述 C语言的头文件有包含顺序吗? C语言的头文件有包含顺序吗?有的话,是什么呀,---------- 解决方案 有包含顺序,即使添加了#ifndef也不一定有用 一个合理的建议:所有的.h中不包含.h,放在CPP中包含.但是每个模块有一个特殊的共通头文件,只用于包含该模块使用的外部的头文件,并且所有的cpp文件必须是最先包含该头文件. 解决方案二: 标注库基本没有包含顺序(差不多都处理掉了), 可以任意使用 但是自己定义的就有了 解决方案三: c语言之头文件包含顺序问题 1.头文件的包含是