私は、Windows上でのみ動作し、Visual Studioでコンパイルされることが知られているコードベースに取り組んでいます(Excelと緊密に統合されているので、どこにも行きません)。私は伝統的なインクルードガードを使うべきか、私たちのコードに#pragma once
を使うべきか疑問に思います。コンパイラに#pragma once
を処理させるとコンパイルが速くなり、コピー&ペースト時にエラーが発生しにくくなります。また、やや見にくいです;)
注:コンパイル時間を短縮するには、 冗長インクルードガード を使用できますが、インクルードファイルとインクルードファイルの間に密接な関係があります。ガードはファイル名に基づくべきで、インクルード名を変更する必要がある場合にのみ変更されるので、通常は問題ありません。
私はそれがコンパイル時間に大きな違いを生むとは思わないが、#pragma once
はコンパイラ間で非常によくサポートされていますが、実際には標準の一部ではありません。あなたの正確な意図を理解するのがより簡単であるので、プリプロセッサはそれで少し速いかもしれません。
#pragma once
は間違いを犯しにくく、入力するコードも少なくなります。
可能であれば、コンパイル時間をもっと短縮するには、.hファイルに含めるのではなく、前方宣言を行ってください。
私は#pragma once
を使うのが好きです。
これを参照してください 両方を使用する可能性についてのウィキペディアの記事 。
私はVSとGCCでコンパイルしていて、かつてインクルードガードを使用していたことをこの議論に追加したいだけでした。私は今#pragma once
に切り替えました、そして私の唯一の理由は私がVSとGCCがそれをサポートする限り標準であるものを本当に気にしないので性能または移植性または標準ではありません、そしてそれはそれです:
#pragma once
はバグの可能性を減らします。
ヘッダーファイルを別のヘッダーファイルにコピーアンドペーストし、必要に応じて修正し、インクルードガードの名前を変更することを忘れないでください。両方が含まれると、エラーメッセージが必ずしも明確ではないため、エラーを追跡するのにしばらく時間がかかります。
#pragma once
に修正不可能なバグがあります。絶対に使わないでください。あなたの#include
検索パスが非常に複雑な場合、コンパイラは同じベース名を持つ2つのヘッダの違いを見分けることができないかもしれません(例えばa/foo.h
とb/foo.h
)、そのうちの1つの#pragma once
は抑制します両方とも。また、2つの異なる相対インクルード(例:#include "foo.h"
と#include "../a/foo.h"
は同じファイルを参照しているため、#pragma once
は必要なときに冗長なインクルードを抑制できないこともわかりません)。
これは#ifndef
ガードでファイルを読み直さないようにするコンパイラの能力にも影響しますが、それは単なる最適化です。 #ifndef
ガードを使用すると、コンパイラは、すでに見たことのない確実でないファイルを安全に読み取ることができます。それが間違っているならば、それはちょうどいくらかの追加の仕事をしなければなりません。 2つのヘッダが同じガードマクロを定義しない限り、コードは期待どおりにコンパイルされます。また、2つのヘッダが同じガードマクロを定義している場合、プログラマはその一方を変更して変更することができます。
#pragma once
にはそのような安全策はありません - コンパイラがヘッダファイルの識別について間違っている場合、いずれにせよ、プログラムはコンパイルに失敗します。このバグに遭遇した場合、あなたの唯一の選択肢は#pragma once
の使用をやめるか、ヘッダの1つを改名することです。ヘッダの名前はAPI規約の一部なので、名前の変更はおそらく選択肢にならないでしょう。
(なぜこれが固定不可能であるかの簡単な説明は、UnixもWindowsファイルシステムAPIもそのようなメカニズムを提供していないということです。 2つの絶対パス名が同じファイルを参照しているかどうかを示すことを保証します。そのためにiノード番号を使用できるという印象を受けている場合は、申し訳ありません。
(歴史的な注意:私がそうする権限を持っていたのに12年前にGCCから#pragma once
と#import
を切り取らなかった唯一の理由は、Appleのシステムヘッダがそれらに頼っていたことでした。 。)
(これがコメントスレッドで2回出てきたので:GCC開発者は#pragma once
をできるだけ信頼できるものにするためにかなりの努力をしました。 GCCバグレポート11569 を参照してください。 GCCのバージョンは、クロックスキューの影響を受けているビルドファームなど、もっともらしい状況ではまだ失敗する可能性があります。だれかがより良いしたとは思わないでしょう。)
#pragma once
が標準になるまで(これは現在の将来の標準の優先事項ではありません)、私はあなたがそれを使用し、警備員を使用することをお勧めします。
#ifndef BLAH_H
#define BLAH_H
#pragma once
// ...
#endif
その理由は:
#pragma once
は標準ではないので、コンパイラによっては機能を提供していない可能性があります。とは言っても、すべての主要なコンパイラがそれをサポートしています。コンパイラがそれを知らない場合は、少なくとも無視されます。#pragma once
には標準的な動作はありませんので、その動作がすべてのコンパイラで同じになるとは限りません。ガードは、少なくともガードに必要なプリプロセッサ命令を実装するすべてのコンパイラに対して、基本的な前提が同じであることを保証します。#pragma once
は(1 cppの)コンパイルを高速化します。これは、コンパイラがこの命令を含むファイルを再度開かないためです。そのため、ファイルに含めると、コンパイラによっては役に立ちます。ガードが検出されたときにg ++が同じ最適化を実行できると聞きましたが、確認する必要があります。この2つを一緒に使うことで、それぞれのコンパイラの長所を生かすことができます。
さて、あなたがガードを生成するための自動スクリプトを持っていないのなら、単に#pragma once
を使うほうが便利かもしれません。それが移植可能なコードにとって何を意味するのかを知ってください。 (私はすぐにガードとプラグマを生成するためにVAssistXを使っています)
あなたは自分のコードを可搬性のある方法で考えるべきだが(未来はどうなるのかわからないので)、他のコンパイラでコンパイルすることを意図していないと本当に思うなら(例えば非常に特殊な組み込みハードウェア用のコード)次に、#pragma once
に関するコンパイラのドキュメントを調べて、実際に何をしているのかを確認してください。
#pragma once
は、ほとんどのコンパイラでサポートされている、インクルードガードよりも短く、エラーが発生しにくく、より速くコンパイルされると言う人もいます(これは真実ではありません)。
しかし、私はあなたが標準の#ifndef
インクルードガードを使うことをまだ提案します。
#ifndef
ですか?A
、B
、およびC
の各クラスがそれぞれ独自のファイル内に存在する、次のような考案されたクラス階層を考えてください。
#ifndef A_H
#define A_H
class A {
public:
// some virtual functions
};
#endif
#ifndef B_H
#define B_H
#include "a.h"
class B : public A {
public:
// some functions
};
#endif
#ifndef C_H
#define C_H
#include "b.h"
class C : public B {
public:
// some functions
};
#endif
さて、あなたが自分のクラスのテストを書いていて、本当に複雑なクラスB
のふるまいをシミュレートする必要があるとしましょう。これを行う1つの方法は、たとえば google mock を使用して モッククラス を作成し、それをディレクトリmocks/b.h
内に配置することです。クラス名は変更されていませんが、別のディレクトリに格納されているだけです。しかし最も重要なことは、インクルードガードが元のファイルb.h
とまったく同じ名前であることです。
#ifndef B_H
#define B_H
#include "a.h"
#include "gmock/gmock.h"
class B : public A {
public:
// some mocks functions
MOCK_METHOD0(SomeMethod, void());
};
#endif
このアプローチでは、元のクラスに触れたり、それについてB
を教えたりせずに、クラスC
の動作をモックすることができます。あなたがしなければならないのは、あなたのコンパイラのインクルードパスにディレクトリmocks/
を置くことだけです。
#pragma once
でできないのですか?#pragma once
を使用すると、クラスB
を元のクラスと偽のバージョンの2回定義することから保護できないため、名前の衝突が発生します。
サポートしていないコンパイラでこのコードを使用しないことに肯定的であれば(Windows/VS、GCC、Clangはdoサポートしているコンパイラの例です)、 #pragmaは、心配せずに必ず1回使用してください。
互換性のあるシステムでは移植性とコンパイルの高速化を図るために、両方を使用することもできます(下記の例を参照)。
#pragma once
#ifndef _HEADER_H_
#define _HEADER_H_
...
#endif
#pragma once
ガードと#ifndef
ガードとの間の想定されるパフォーマンスのトレードオフと正しさの引数に関する拡張議論に従事した後(私はいくつかの比較的最近の教化に基づいて#pragma once
の側をとっていましたそのために、コンパイラは既に含まれているファイルを再_#pragma once
しようとする必要がないため、#include
がより高速であるという理論を最終的にテストすることにしました。
テストのために、複雑な相互依存関係を持つ500個のヘッダーファイルを自動的に生成し、.c
ファイルですべてが#include
sになるようにしました。テストは3つの方法で実行しました。1回は#ifndef
で、1回は#pragma once
で、1回は両方で実行しました。私はかなり最新のシステム(XCodeにバンドルされているClangを使用し、内部SSDを使用してOSXを実行している2014 MacBook Pro)でテストを実行しました。
まず、テストコード:
#include <stdio.h>
//#define IFNDEF_GUARD
//#define PRAGMA_ONCE
int main(void)
{
int i, j;
FILE* fp;
for (i = 0; i < 500; i++) {
char fname[100];
snprintf(fname, 100, "include%d.h", i);
fp = fopen(fname, "w");
#ifdef IFNDEF_GUARD
fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i);
#endif
#ifdef PRAGMA_ONCE
fprintf(fp, "#pragma once\n");
#endif
for (j = 0; j < i; j++) {
fprintf(fp, "#include \"include%d.h\"\n", j);
}
fprintf(fp, "int foo%d(void) { return %d; }\n", i, i);
#ifdef IFNDEF_GUARD
fprintf(fp, "#endif\n");
#endif
fclose(fp);
}
fp = fopen("main.c", "w");
for (int i = 0; i < 100; i++) {
fprintf(fp, "#include \"include%d.h\"\n", i);
}
fprintf(fp, "int main(void){int n;");
for (int i = 0; i < 100; i++) {
fprintf(fp, "n += foo%d();\n", i);
}
fprintf(fp, "return n;}");
fclose(fp);
return 0;
}
そして今、私のさまざまなテストが実行されます:
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.164s
user 0m0.105s
sys 0m0.041s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.140s
user 0m0.097s
sys 0m0.018s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.193s
user 0m0.143s
sys 0m0.024s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.153s
user 0m0.101s
sys 0m0.031s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.170s
user 0m0.109s
sys 0m0.033s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.155s
user 0m0.105s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.153s
user 0m0.101s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.181s
user 0m0.133s
sys 0m0.020s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.167s
user 0m0.119s
sys 0m0.021s
folio[~/Desktop/pragma] fluffy$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-Apple-darwin17.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
ご覧のとおり、#pragma once
を含むバージョンは、#ifndef
のみのプリプロセスよりも実際にわずかに高速でしたbut違いはごくわずかであり、実際にコードを構築してリンクするのにかかる時間。おそらく十分に大きなコードベースでは、実際にはビルド時間に数秒の差が生じる可能性がありますが、最新のコンパイラーは#ifndef
ガードを最適化できるため、OSには良好なディスクキャッシュがあり、ストレージ技術では、少なくとも今日の時代の典型的な開発者システムでは、パフォーマンスの議論は重要ではないと思われます。より古く、よりエキゾチックなビルド環境(例:ネットワーク共有でホストされているヘッダー、テープからのビルドなど)は、方程式を多少変更する可能性がありますが、そのような状況では、最初に脆弱性の少ないビルド環境を作成する方が便利です。
問題の事実は、#ifndef
は標準の動作で標準化されているのに対し、#pragma once
はそうではなく、#ifndef
も奇妙なファイルシステムと検索パスのコーナーケースを処理しますが、#pragma once
特定の事柄に混乱し、プログラマーが制御できない不正な動作につながります。 #ifndef
の主な問題は、(名前の衝突などで)ガードに悪い名前を選択するプログラマーであり、それでもAPIのコンシューマーが#undef
を使用してそれらの悪い名前をオーバーライドすることは可能です。おそらく完璧な解決策ですが、可能ですが、コンパイラが誤って#pragma once
を間引きしている場合、#include
に頼ることはできません。
したがって、にもかかわらず#pragma once
は明らかに(わずかに)高速ですが、これが#ifndef
ガードで使用する理由であることに同意しません。
EDIT:@LightnessRacesInOrbitからのフィードバックのおかげで、ヘッダーファイルの数を増やし、プリプロセッサステップのみを実行するようにテストを変更し、コンパイルによって追加された短い時間を排除しました。およびリンクプロセス(以前は簡単でしたが、現在は存在しません)。予想どおり、差はほぼ同じです。
私のコードはMSVCやGCC以外のものでコンパイルしなければならないことがあるので、私は一般的に#pragma once
を気にする必要はありません(組み込みシステム用のコンパイラは常に#pragmaを持っているとは限りません)。
それで私はとにかく#includeガードを使わなければなりません。いくつかの回答が示すように#pragma once
を使用することもできますが、それほど理由がないように思われ、それをサポートしていないコンパイラには不要な警告が表示されることがよくあります。
このプラグマがどのくらいの時間節約になるかはわかりません。一般的に、コンパイラはヘッダにガードマクロの外側にコメント以外の何もないことを認識しており、その場合は#pragma once
と同等の処理を実行する(つまり、ファイルを再び処理しない)ことをすでに聞いています。しかし、それが本当かどうか、コンパイラの場合がこの最適化を実行できるかどうかはわかりません。
どちらの場合でも、どこでも動作し、それ以上心配することのない#includeガードを使用するほうが簡単です。
私はあなたが最初にすべきことはこれが本当に違いを生むことになっているかどうかを確認することであると思います。まずパフォーマンスをテストする必要があります。 Googleでの検索の1つが失敗しました this 。
結果のページでは、コラムはほとんどわかりませんが、少なくともVC6まで、マイクロソフトは他のツールが使用していたインクルードガードの最適化を実装していませんでした。インクルードガードが内部にある場合は、インクルードガードが外部にある場合と比較して50倍の時間がかかりました(外部のインクルードガードは少なくとも#pragmaと同等です)。しかし、これによる影響を考えてみましょう。
提示された表によると、インクルードを開いてそれをチェックする時間は、#pragmaと同等の時間の50倍です。しかし、実際の所要時間は1999年当時のファイルあたり1マイクロ秒でした。
それで、単一のTUにいくつの重複ヘッダがあるでしょうか。これはあなたのスタイルによりますが、平均的なTUが100回重複していると言うと、1999年にはTUあたり100マイクロ秒かかる可能性があります。 HDDの改良により、これはおそらく今ではかなり低くなりますが、それでもプリコンパイルされたヘッダと正しい依存関係の追跡でプロジェクトのためのこれの総累積コストを追跡することはあなたのビルド時間のほんのわずかな部分です。
逆に、#pragma once
をサポートしていないコンパイラに移行した場合、#pragmaではなくガードを含めるようにソースベース全体を更新するのにどのくらい時間がかかるのかを考えてみてください。
MicrosoftがGCCや他のすべてのコンパイラが行うのと同じ方法でインクルードガード最適化を実装できなかった理由はありません(より新しいバージョンがこれを実装しているかどうかを実際に確認できる人はいますか?)。私見、#pragma once
はあなたの代わりのコンパイラの選択を制限すること以外ほとんど何もしません。
#pragma once
を使用すると、#includeガードに到達するまでファイルを解析するのではなく、コンパイラはファイルが再度発生したときにそのファイルを完全にスキップできます。
そのため、セマンティクスは少し異なりますが、それらが使用されることを意図した方法で使用されている場合、それらは同一です。
最悪の場合(警告ではなく、未知のプラグマを実際のエラーとしてフラグ付けするコンパイラ)のように、両方を組み合わせることがおそらく最も安全な方法です。#プラグマ自体を削除する必要があります。
あなたのプラットフォームを「デスクトップの主流のコンパイラ」に限定するとき、あなたは安全に#includeガードを省略することができます、しかし私もそれについて不安を感じます。
OT:他にもビルドをスピードアップするためのヒントや経験があるのであれば、興味があるでしょう。
#pragmaを1回使用してガードを一緒に入れたい人のために:MSVCを使用していないのであれば、#pragmaから1回はあまり最適化されません。
そして、あなたは "#pragma once"をヘッダに入れるべきではありません。
こちら は#pragma once usageについての例を含む詳細な議論です。
Konrad Kleine による説明の上。
簡単な要約
# pragma once
を使うとき、それがコンパイラの責任の大部分であり、それを2回以上含めることを許しません。つまり、ファイル内のコードスニペットについて言及した後は、それはあなたの責任ではなくなります。さて、コンパイラはファイルの先頭でこのコードスニペットを探し、それがインクルードされるのをスキップします(すでにインクルードされている場合)。これは間違いなく(平均して巨大なシステムで)コンパイル時間を減らすでしょう。ただし、モック/テスト環境の場合、循環などの依存関係のためにテストケースの実装が困難になります。
#ifndef XYZ_H
を使うとき、ヘッダの依存関係を維持するのは開発者の責任です。つまり、何らかの新しいヘッダファイルが原因で循環依存の可能性があるときはいつでも、コンパイラはコンパイル時に「undefined ..
」エラーメッセージにフラグを立てるだけです。不適切なものが含まれています。これは間違いなくコンパイル時間を増やします(修正して再実行する必要があるため)。また、 "XYZ_H"の定義済み状態に基づいてファイルをインクルードすることに基づいて動作しますが、すべての定義を取得できない場合は、まだ文句を言います。
したがって、このような状況を回避するには、次のように使用します。
#pragma once
#ifndef XYZ_H
#define XYZ_H
...
#endif
すなわち両方の組み合わせ。