読者です 読者をやめる 読者になる 読者になる

にゃははー

はへらー

インクルードガードとpragma once

C++ Advent Calendar

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 once1行の手軽さからと登場の速さもあってか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が標準化されてもそれを実装したコンパイラが出てくるまでに若干の時間がかかるので五十歩百歩といったところでしょう。

まとめ

なお最初に例示したインクルードガードは正しく動作しません。

参照

*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