#pragma onceは使って良い
C言語もしくはC++では、#pragma once
という非標準のプリプロセッサディレクティブがある。
これは同じファイル(ふつうはヘッダファイル)が二重にインクルードされるのを防ぐ機能がある。
#pragma once
は非標準ではあるが、多くの環境でサポートされているため、使って良い。
むしろ、今では、よほど特殊な事情がなければ、積極的に使うべきだと考えている。
標準的なインクルードガードは次のとおりである。
#ifndef THIS_HEADER_INCLUDED
#define THIS_HEADER_INCLUDED
#endif // THIS_HEADER_INCLUDED
初学の頃私は、「可搬性を高めるために#pragma once
は使わずにこの標準的なインクルードガードを使用する」というふうに教わった。
しかし、この標準的なインクルードガードでは、使用するシンボルが意図せず他のヘッダファイルと重複してしまうと、 一方のヘッダファイルが読み込まれず、少し厄介なバグやエラーに発展することがある。
見本となる別のヘッダファイルをコピーして、それをベースにしてヘッダファイルを作成する場合などで、このシンボルを書き換えるのを忘れてしまったときなどに、 この手のエラーが発生した経験が何度かある。
その他#ifndef
を#ifdef
としてしまって永遠にヘッダファイルが読まれなかったり、あるいは一行目と二行目のシンボルでタイプミスをしてしまって、インクルードガードがじつは働いていなかったり、といった凡ミスもある。
#pragma once
ではシンボルを定義しないので、原理的にこの手のエラーは発生しない。
この点において、#pragma once
は良い。
また、標準のインクルードガードについては、それ自体の問題ではないが、次のようなシンボルが使われがちなのも問題である。
#ifndef __THIS_HEADER_INCLUDED__
#define __THIS_HEADER_INCLUDED__
#endif // __THIS_HEADER_INCLUDED__
__THIS_HEADER_INCLUDED__
というシンボルは、実は予約済み識別子である。
予約済み識別子については、C++の規格書 (ISO/IEC 14882:2023)では$5.10 Identifiersの項に次のように書かれている。
In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required.
- Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use.
- Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
さらに、トークンまたはプリプロセッシングトークンとして現れる一部の識別子は、C++実装での使用のために予約されており、それ以外で使用してはなりません。この違反について診断メッセージは必須ではありません。
- 二つの連続したアンダースコア __ を含む識別子、またはアンダースコアに続けて大文字で始まる識別子は、実装のために予約されています。
- アンダースコアで始まる識別子は、グローバル名前空間における名前として実装のために予約されています。
また、C言語の規格書 (ISO/IEC 9899:2024)では$6.4.2 Identifiersの項に次のように書かれている。
Some identifiers are reserved.
- All identifiers that begin with a double underscore (__) or begin with an underscore (_) followed by an uppercase letter are reserved for any use, except those identifiers which are lexically identical to keywords.
- All identifiers that begin with an underscore are reserved for use as identifiers with file scope in both the ordinary and tag name spaces.
いくつかの識別子は予約されています。
- 二つの連続したアンダースコア(__)で始まる識別子、またはアンダースコア(_)に続けて大文字で始まる識別子は、キーワードと字句的に同一のものを除き、いかなる用途でも予約されています。
- アンダースコア(_)で始まるすべての識別子は、通常の名前空間およびタグ名前空間の両方において、ファイルスコープの識別子として使用するために予約されています。
いずれの場合においても、二つの連続したアンダースコアで始まる識別子は予約されており、__THIS_HEADER_INCLUDED__
はこのルールに当てはめると予約済み識別子である。
_THIS_HEADER_INCLUDED_
というのもよく見るが、これもだめである。アンダースコアに続けて大文字で始まる識別子は予約されている。
「標準にない#pragma onceを使うな」と言いつつ、この標準違反の識別子を使用したインクルードガードを書く人もいるので、この慣習はなかなかに根深いものだと思われる。
#pragma once
に欠点がないわけではない。
標準化されていない以上、コンパイラ依存の挙動は避けられず、特にシンボリックリンクがソースコードに含まれている場合などに、ファイルの同一性の判定がコンパイラによって異なる場合があり、厄介なエラーに発展する潜在的な可能性はある。
しかし、少なくとも私の経験上は、これが問題に発展した経験はなく、いっぽうで標準的なインクルードガードはシンボルの重複によって問題となった経験は何度かある。
シンボリックリンクやハードリンクあるいはネットワークファイルシステムを使用するような非常に大規模なコードベース、しかも複数のコンパイラを使用する必要があるような場合を除けば、#pragma once
を使用することは有利であると考えられ、基本的には推奨したいと考えている。