閱讀107 返回首頁    go 阿裏雲 go 技術社區[雲棲]


程序設計方式 之 嵌套頭文件包含方式

       一直以來寫程序有個習慣,喜歡把常用的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
 

最後更新:2017-04-02 00:06:29

  上一篇:go PHP擴展導出類
  下一篇:go 四年了,工作的迷茫。