C++ヘッダファイルのインクルード順序に関する研究

黄舟
リリース: 2017-02-06 14:11:09
オリジナル
2795 人が閲覧しました

1. 「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, 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++ 標準ライブラリ、他のライブラリのヘッダ ファイル、および独自のプロジェクトのヘッダ ファイルの順序を使用する必要があります。ただし、優先ヘッダー ファイルが最初にインクルードされます。つまり、たとえば、.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. ヘッダー ファイルをインクルードするときは、ヘッダー ファイルが配置されているプロジェクトのフォルダー名を追加する必要があります。 logging.h がその中にある場合、このヘッダー ファイルの外部インクルードは次のように記述する必要があります: #include "logging.h" の代わりに #include "base/logging.h"

私たちが目にしているのは、背後にある隠された目的です。 「Google C++ プログラミング スタイル ガイド」で提唱されている原則:


1 隠れた依存関係を減らし、ヘッダー ファイルとその実装ファイルを一致させるには、その設定 (つまり、対応するヘッダー ファイル) を次のようにする必要があります。最初に含まれています。

2. 好みに加えて、一般から特定への原則に従います。ただし、「Google C++ プログラミング スタイル ガイド」の順序: C 標準ライブラリ、C++ 標準ライブラリ、他のライブラリのヘッダ ファイル、独自のプロジェクトのヘッダ ファイルの最初の項目: オペレーティング システム レベルのヘッダ ファイルが抜け落ちていると思います。上記の例 sys/types.h は、おそらく C 標準ライブラリに分類できませんが、Linux オペレーティング システムによって提供される SDK です。したがって、より正確な記述は、OS SDK .h、C 標準ライブラリ、C++ 標準ライブラリ、他のライブラリのヘッダ ファイル、および独自のプロジェクトのヘッダ ファイルであるべきだと思います。


3. ヘッダファイルが配置されているプロジェクトディレクトリをリストする必要がある理由は、誤って重複したファイル名を区別するために、名前空間が同じである必要があるためです。

2. 『C++ プログラミング思考』のさまざまな視点

『Google C++ プログラミング スタイル ガイド』とは異なり、『C++ プログラミング思考』は別のルールを提唱しています。 『C++ プログラミングの考え方』P432 に記載:

ヘッダー ファイルがインクルードされる順序は、「最も具体的なものから最も一般的なもの」の順です。つまり、ローカル ディレクトリ内のヘッダー ファイルが最初に組み込まれます。次に、独自の「ツール」ヘッダーがすべて登場し、その後にサードパーティのライブラリ ヘッダー、さらに標準の C++ ライブラリ ヘッダーと C ライブラリ ヘッダーが続きます。

理由を理解するには: 「Large Scale C++ Software Design」の John Lakos の一節を読むことができます (注: その中国語訳は「Large Scale C++ Programming」です):

.h ファイルのコンポーネントが破壊されていないことを確認してください。単独で解析することで、潜在的な使用上のエラーを回避します。解析自体には、明示的に提供された宣言や定義が欠けているためです。 .c ファイルの最初の行に .h ファイルを含めることで、コンポーネントの物理インターフェイスにとって重要なすべての内部情報ブロックが .h に確実に含まれるようになります (一部の情報ブロックが実際に欠落している場合は、.c ファイルを作成した後でコンパイルできます)。ファイルにはこの問題が見つかりました)。

ヘッダー ファイルをインクルードする順序が「最も具体的なものから最も一般的なものへ」の場合、ヘッダー ファイルがそれ自体で解析されない場合。すぐに発見し、トラブルを未然に防ぎます。

3つ。私の実験


どの順序が良いですか? 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"
ログイン後にコピー

の場合、コンパイルは成功します。デバッグおよび実行すると、メインの関数呼び出しは依然として C 標準ライブラリの関数 acos です。関数呼び出しの順序はヘッダー ファイルのインクルード順序に基づいているようです。つまり、カスタム acos 関数が上書きされます。 if TestInc.h contains 関数が接続されている場合、インライン関数が最初に呼び出されます)。


この小さな実験から、私は次の結論に達しました: 『Google C++ プログラミング スタイル ガイド』と『C++ プログラミング思想』で提唱されているヘッダー ファイルのインクルード順序にはそれぞれ利点があり、『Google C++ プログラミング スタイル』 「ガイド」では、隠れたヘッダー ファイルの依存関係を減らすことができるはずです。また、「C++ プログラミングの考え方」では、定義したインターフェイスがシステム ライブラリやサードパーティ ライブラリと競合するかどうかを簡単に知ることができます。


4つ。ヘッダー ファイル インクルードのプリコンパイル機能


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++ 头文件的包含顺序研究的内容,更多相关内容请关注PHP中文网(www.php.cn)!


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート