C++ 標頭檔的包含順序研究

來源:互聯網
上載者:User
一.《Google C++ 編程風格指南》裡的觀點


公司在推行編碼規範,領導提議基本上使用《Google C++ 編程風格指南》。其中《Google C++ 編程風格指南》對於標頭檔的包含順序是這樣的:

Names and Order of Includeslink ▽Use standard order for readability and to avoid hidden dependencies:C library, C++ library, other libraries’ .h, your project’s .h.All of a project’s header files should belisted as descendants of the project’s source directory without use of UNIXdirectory shortcuts . (the current directory) or .. (the parent directory). Forexample, google-awesome-project/src/base/logging.h should be included as#include “base/logging.h”In dir/foo.cc or dir/foo_test.cc, whosemain purpose is to implement or test the stuff in dir2/foo2.h, order yourincludes as follows:dir2/foo2.h (preferred location — seedetails below).C system files.C++ system files.Other libraries’ .h files.Your project’s .h files.The preferred ordering reduces hiddendependencies. We want every header file to be compilable on its own. Theeasiest way to achieve this is to make sure that every one of them is the first.h file #included in some .cc.dir/foo.cc and dir2/foo2.h are often in thesame directory (e.g. base/basictypes_test.cc and base/basictypes.h), but can bein different directories too.Within each section it is nice to order theincludes alphabetically.For example, the includes ingoogle-awesome-project/src/foo/internal/fooserver.cc might look like this:#include "foo/public/fooserver.h"  // Preferred location.#include <sys/types.h>#include <unistd.h>#include <hash_map>#include <vector>#include "base/basictypes.h"#include"base/commandlineflags.h"#include "foo/public/bar.h"


在這裡我談一下我對上面的理解(如不當,還請諸位同學指正):


1. 為了加強可讀性和避免隱含依賴,應使用下面的順序:C標準庫、C++標準庫、其它庫的標頭檔、你自己工程的標頭檔。不過這裡最先包含的是首選的標頭檔,即例如a.cpp檔案中應該優先包含a.h。首選的標頭檔是為了減少隱藏依賴,同時確保標頭檔和實現檔案是匹配的。具體的例子是:假如你有一個cc檔案(linux平台的cpp檔案尾碼為cc)是google-awesome-project/src/foo/internal/fooserver.cc,那麼它所包含的標頭檔的順序如下:

#include <sys/types.h>  #include <unistd.h>    #include <hash_map>  #include <vector>    #include "base/basictypes.h"  #include "base/commandlineflags.h"  #include "foo/public/bar.h"


2. 在包含標頭檔時應該加上標頭檔所在工程的檔案夾名,即假如你有這樣一個工程base,裡面有一個logging.h,那麼外部包含這個標頭檔應該這樣寫:

#include “base/logging.h”,而不是#include “logging.h”

我們看到的是這裡《Google C++ 編程風格指南》倡導的原則背後隱藏的目的是:


1. 為了減少隱藏依賴,同時標頭檔和其實現檔案匹配,應該先包含其喜好設定(即其對應的標頭檔)。

2. 除了喜好設定外,遵循的是從一般到特殊的原則。不過我覺得《Google C++ 編程風格指南》的順序:C標準庫、C++標準庫、其它庫的標頭檔、你自己工程的標頭檔中漏了最前面的一項:作業系統層級的標頭檔,比如上面的例子sys/types.h估計不能歸入C標準庫,而是Linux作業系統提供的SDK吧。因此我覺得更準確的說法應該是:OS SDK .h , C標準庫、C++標準庫、其它庫的標頭檔、你自己工程的標頭檔。


3.之所以要將標頭檔所在的工程目錄列出,作用應該是命名空間是一樣的,就是為了區分不小心造成的檔案重名。

二.《C++編程思想》中的不同觀點

與《Google C++ 編程風格指南》不同的是,《C++編程思想》倡導一種不同的規則。《C++編程思想》P432提到:

標頭檔被包含的順序是從“最特殊到最一般”。這就是,在本地目錄的任何標頭檔首先被包含。然後是我們自己的所有“工具”標頭檔,隨後是第三方庫標頭檔,接著是標準C++庫標頭檔和C庫標頭檔。

要瞭解其原因:可以看JohnLakos在《Large ScaleC++ Softwre Design》(註:其中文譯名為《大規模C++程式設計》)中的一段話:

保證.h檔案的組成部分不被它自身解析(parse),這可以避免潛在的使用錯誤。因為被自身解析缺乏明確提供的聲明或定義。在.c檔案的第一行包含.h 檔案能確保所有對於構件的物理介面重要的內部資訊塊都在.h中(如果的確是缺少了某些資訊塊,一旦編譯這個.c檔案時就可以發現這個問題)。

如果包含標頭檔的順序是“從最特殊到最一般”,如果我們的標頭檔不被它自己解析。我們將馬上找到它,防止麻煩事情發生。

三.我的實驗


到底哪一種包含順序好呢?我使用VS 2005編一個控制台測試工程TestInc,裡面有幾個檔案。


MyMath.h的代碼如下:

#pragma once  double acos(double Num);MyMath.cpp的代碼如下:double acos(double Num)  {      return 1.0;  }


TestInc.cpp的代碼如下:

#include "TestInc.h"  #include <stdio.h>  #include <math.h>    int _tmain(int argc, _TCHAR* argv[])  {      double a = acos(0.5);      return 0;  }

結果出現錯誤:

1>c:program filesmicrosoft visualstudio 8vcincludemath.h(107) : error C2732: 連結規範與“acos”的早期規範衝突1>       c:program filesmicrosoft visual studio 8vcincludemath.h(107) : 參見“acos”的聲明


然後我把TestInc.cpp的標頭檔包含順序改為:

#include <stdio.h>  #include <math.h>  #include "TestInc.h"

則編譯通過了。在調試運行時main函數調用還是C標準庫的函數acos,看來函數調用的順序是按標頭檔的包含順序來的,即我自訂的acos函數被覆蓋了(如果TestInc.h裡包含了內嵌函式,則優先調用的是內嵌函式)。


從這個小實驗中我得出如下結論:《Google C++ 編程風格指南》和《C++編程思想》倡導的包含標頭檔的順序各有優點,《Google C++ 編程風格指南》應該能大量減少隱藏的標頭檔依賴,而《C++編程思想》則很容易讓你清楚知道你所定義的介面是否和系統庫及第三方庫發生衝突。


四.標頭檔包含中的先行編譯功能


在Visual Studio環境下開發我們發現幾乎每個cpp檔案都要包含stdafx.h這個檔案,而且要把它放在最前面的位置,否則就會出錯。這是為什麼呢?

原來Visual Studio採用一種先行編譯的機制。要瞭解先行編譯機制,先介紹一下先行編譯頭。所謂的先行編譯頭就是把一個工程中的那一部分代碼,預先編譯好放在一個檔案裡(通常是以.pch為副檔名的),這個檔案就稱為先行編譯標頭檔這些預先編譯好的代碼可以是任何的C/C++代碼,甚至是inline的函數,但是必須是穩定的,在工程開發的過程中不會被經常改變。如果這些代碼被修改,則需要重新編譯產生先行編譯標頭檔。注意產生先行編譯標頭檔是很耗時間的。同時你得注意先行編譯標頭檔通常很大,通常有6- 7M大。注意及時清理那些沒有用的先行編譯標頭檔。


也許你會問:現在的編譯器都有Time stamp的功能,編譯器在編譯整個工程的時候,它只會編譯那些經過修改的檔案,而不會去編譯那些從上次編譯過,到現在沒有被修改過的檔案。那麼為什麼還要先行編譯標頭檔呢?答案在這裡,我們知道編譯器是以檔案為單位編譯的,一個檔案經過修改後,會重新編譯整個檔案,當然在這個檔案裡包含的所有標頭檔中的東西(.eg Macro, Preprocessor )都要重新處理一遍。 VC的先行編譯標頭檔儲存的正是這部分資訊。以避免每次都要重新處理這些標頭檔。

根據上文介紹,先行編譯標頭檔的作用當然就是提高便宜速度了,有了它你沒有必要每次都編譯那些不需要經常改變的代碼。編譯效能當然就提高了。

要使用先行編譯頭,我們必須指定一個標頭檔,這個標頭檔包含我們不會經常改變的代碼和其他的標頭檔,然後我們用這個標頭檔來產生一個先行編譯標頭檔(.pch 檔案)想必大家都知道StdAfx.h這個檔案。很多人都認為這是VC提供的一個“系統層級”的,編譯器帶的一個標頭檔。其實不是的,這個檔案可以是任何名字的。我們來考察一個典型的由AppWizard產生的MFC Dialog Based 程式的先行編譯標頭檔。(因為AppWizard會為我們指定好如何使用先行編譯標頭檔,預設的是StdAfx.h,這是VC起的名字)。我們會發現這個標頭檔裡包含了以下的標頭檔:

#include <afxext.h> // MFC extensions  #include <afxdisp.h> // MFC Automation classes  #include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls #include <afxcmn.h>


這些正是使用MFC的必須包含的標頭檔,當然我們不太可能在我們的工程中修改這些標頭檔的,所以說他們是穩定的。


那麼我們如何指定它來產生先行編譯標頭檔。我們知道一個標頭檔是不能編譯的。所以我們還需要一個cpp檔案來產生.pch 檔案。這個檔案預設的就是StdAfx.cpp。在這個檔案裡只有一句代碼就是:#include“Stdafx.h”。原因是理所當然的,我們僅僅是要它能夠編譯而已―――也就是說,要的只是它的.cpp的副檔名。我們可以用/Yc編譯開關來指定StdAfx.cpp來產生一個.pch檔案,通過/Fp 編譯開關來指定產生的pch檔案的名字。開啟project ->Setting->C/C++ 對話方塊。把Category指向Precompiled Header。在左邊的樹形視圖裡選擇整個工程,Project Options(右下角的那個白的地方)可以看到 /Fp “debug/PCH.pch”,這就是指定產生的.pch檔案的名字,預設的通常是 .pch。然後,在左邊的樹形視圖裡選擇 StdAfx.cpp,這時原來的Project Option變成了 Source File Option(原來是工程,現在是一個檔案,當然變了)。在這裡我們可以看到 /Yc開關,/Yc的作用就是指定這個檔案來建立一個Pch檔案。/Yc後面的檔案名稱是那個包含了穩定代碼的標頭檔,一個工程裡只能有一個檔案的可以有 YC開關。VC就根據這個選項把 StdAfx.cpp編譯成一個Obj檔案和一個PCH檔案。


這樣,我們就設定好了先行編譯標頭檔。也就是說,我們可以使用先行編譯頭功能了。以下是注意事項:


1)如果使用了/Yu,就是說使用了先行編譯,我們在每個.cpp檔案的最開頭,包含你指定產生pch檔案的.h檔案(預設是stdafx.h)不然就會有問題。如果你沒有包含這個檔案,就告訴你Unexpected file end.

2)如果你把pch檔案不小心丟了,根據以上的分析,你只要讓編譯器產生一個pch檔案就可以了。也就是說把stdafx.cpp(即指定/Yc的那個cpp檔案)重新編譯一遍就可以了。


那麼在Linux平台下有沒有這種先行編譯機制呢?如果有,它是怎麼實現的呢?Linux平台下GCC編譯器也實現了先行編譯機制的。這裡以開源IDE CodeBlocks(CodeBlocks內建了GCC編譯器)的工程為例來說明Linux平台的實現:

使用CodeBlocks建一個C++工程,然後建立一個my_pch.h,輸入如下代碼:

/***************************************************************  * Name:      my_pch.h  * Purpose:   Header to create Pre-Compiled Header (PCH)  * Author:     ()  * Created:   2010-10-26  * Copyright:  ()  * License:  * 使用方法: 項目構建選項-->其他選項-->填入下面兩行  -Winvalid-pch  -include my_pch.h  **************************************************************/    #ifndef MY_PCH_H_INCLUDED  #define MY_PCH_H_INCLUDED    // put here all your rarely-changing header files    #include <iostream>  #include <string>    #endif


然後在項目構建選項–>其他選項–>填入下面兩行

-Winvalid-pch-include my_pch.h

就可以啟用先行編譯檔案頭。


然後 main.cpp 就可以不用 include 標頭檔了,直接這樣就可以編譯了

int main()  {   using namespace std;      cout << "Hello world!" << endl;      return 0;  }


即使在上面的代碼寫上下面一行,其實是不起作用的:

#include <iostream>

以上就是C++ 標頭檔的包含順序研究的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.