インクルードガードとpragma once
C++ Advent Calendar 2015の5日目です。
前C++時代から近代C++に至るまで、ヘッダファイルの重複インクルード排除のために通称インクルードガードというものが使われてきました。
#ifndef YOUR_VERY_VERY_AWESOME_LIBRARY_HEADER_H #define YOUR_VERY_VERY_AWESOME_LlBRARY_HEADER_H #endif // YOUR_VERY_VERY_AWESOME_LIBRARY_HEADER_H
しかしこれはファイルの前後に書かないといけないという制約や、他のヘッダとトークン列が一致してしまうと意図せずヘッダがインクルードされないという問題がありました。
これに対してたった1行で書いて終わりの#pragma once
は、標準化を求める声も多く、目的が明確で、実装もあるにも関わらず、標準に入る気配すらありません。
twitterとかでもそういう声をたまに見るのですが、ちょっと考えれば難しそうだというのがわかってもらえるかと思うので、ここでまとめておこうと思います。
pragma onceの登場
いつから#pragma once
が登場したのかは知らないですが、そのうちコンパイラ考古学でもしたいなと思っているので、覚えてたら調べるかもしれないです。
まぁ少なくとも最近の有名なコンパイラはまず持っているでしょう。
#pragma once
1行の手軽さからと登場の速さもあってかMSVC環境下では使用が推奨されているように書かれているところもあります。
むしろMSVCだけなので_MSC_VER
見て#ifdef
しろという記述すら見受けられます。
また一部では#pragma once
は2回目の時プリプロセッサが展開を行おうとしないので早いみたいな記述も見受けられますが、現代のプリプロセッサではそんなことは百も承知で、if-defined-endifの流れを見つけると完全に省略する最適化をすでに実装しています*1。
まぁパフォーマンス上の利点は今となっては無くなってはいますが、やはり1行で書けるという圧倒的手軽さも相まって#pragma once
の標準化は様々なところで求められています*2。
なんでpragma onceを標準化できないのか
標準に含まれているプリプロセッサのディレクティブのみを使用してインクルードガードは実現されていますが、同等の機能を持つ#pragma once
は一向に標準化されません。
それはなぜでしょうか。
もともとインクルードガードで世の中がうまく回っていたのは、なにも同じファイルの二重インクルードを回避することが本来の目的ではないからです。 例えば以下の様なヘッダファイルは何回インクルードされようが一向に問題ありません。
typedef int INT; namespace hoge { struct AWESOME; void feature(); }
問題が発生するのはそれらファイルに定義が含まれていた時です。 宣言は全ての宣言が同一であれば何回宣言されても問題無いですが、定義は同一であっても複数回定義することはできません。 この定義の重複を避けるのがインクルードガードの本来の目的です。
なんで標準化できないかという話に戻りますが、#pragma once
は同一ファイルの二重インクルードを回避することはできても、定義の重複を回避することができないことが原因です。
もちろん、そもそもファイルの同一性というのが難しい話で*3、(絶対|相対)パス、シンボリックリンク、ハードリンク、ジャンクション(bind mount)、builtin、、、と、条件は多くなってしまいます*4。
この問題が一番起こりやすいのはディストリビューションがシステムとして組み込んでいるライブラリがあった時に、あるソフトウェアがそのライブラリ自体をバンドルしてtarballで再配布していたりする時です、例えばzlibとか*5。
さて、これ以外にもまだ挙動が明確ではない場合があります。
それは#pragma once
が#ifdef - #endif
の内側にあった時です。
強引に1ファイルに納めていますが、例えばこのようになったり、でも#pragma once
の位置が変わるとこうなります。
まぁこんなコード書く奴はわかっててやっていると思うのですが、それでもほんとにそれでいいのかというとどうなんだろうとなります。
じゃぁほんとにどうしても標準に組み込めないのか
というとそういうわけでもなく、議論中に出ていた意見では#define
で定義したようなシンボルを渡せるようにすればいいのではというのがあって、恐らくこれが一番有力な気がするのですが、どうも提案にまではこぎつけていないよう?*6
#pragma once UNIQUE_TOKEN
そもそも#pragma
は中身が全部実装自由なディレクティブだから#once
や#guard
を作ろうという意見もあったようです*7。
#once <tag> // 必ずファイルの先頭になければならない
#guard <tag> // if-define-endifの省略形
#endguard
更にはデフォルトでonce
にしてしまって、必要に応じて#pragma multiple
を指定してインクルードをしてはどうかという意見もありました*8。
pragma onceの今後
最近ではpragma onceの話題すら出ていないように思います。
このままたち消えになってしまうという懸念はありますが、Moduleが来ればそもそもヘッダファイルなどという概念はなくなるはずなので、気にするほどのことでもないでしょう。
Module来るんかっていう疑問はありますが、結局#pragma once
が標準化されてもそれを実装したコンパイラが出てくるまでに若干の時間がかかるので五十歩百歩といったところでしょう。
まとめ
なお最初に例示したインクルードガードは正しく動作しません。
参照
- ISO C++ Standard - Discussion › Standardize #pragma once or some equivalent
- ISO C++ Standard - Future Proposals › #pragma once
- Once-Only Headers - The C Preprocessor
- [C, C++] インクルードガード実装毎のベンチマーク - Qiita
*1:https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
*2:ちょっと調べただけでもでてきます。 #pragma once standard - Google 検索
*3:ファイルシステム的には簡単な話だと思うけどファイルシステム屋じゃないので断定はできないです
*4:しかし我々には完全無欠のimplementation-definedがあるのだが
*5:いやそもそもサーチパス何とかするように--with-system-*とかをconfigure.ac書けよっていうのはもっともですが
*6:私が追いつけていないので取り逃してるかもしれないですが
*7:https://groups.google.com/a/isocpp.org/d/msg/std-proposals/EYK9YeOozHk/Vb5Kj_Zc_bUJ
*8:https://groups.google.com/a/isocpp.org/d/msg/std-proposals/EYK9YeOozHk/1wLawN6wSZEJ