お品書き
1. はじめに
2. インクルードガードとは
3. スニペット紹介
4. おわりに
はじめに
仕事でもプライベートでも Visual Studio Code (以下、VSCode) を開発環境として使用している。
自分が使いたいプログラミング言語は大体サポートされているし、不慣れな IDE を開くよりも高機能に作りこむこともできる。
しかも設定情報は JSON ファイルで管理されているので、どの環境に移っても設定ファイルさえ持ってくれば今までと同じように使える。
なので移植性という面でも高評価である。
そんなわけで C++ でコーディング作業をするときも大体 VSCode を使用するのだが、ヘッダファイルを追加するときに毎回書くインクルードガードが煩わしかったので、個人的に最大限手間を省けるスニペットを作成した。
自分以外にも同じような煩わしさに悶えている人は多いのではないかと思う。
そんな人にはぜひ今回紹介するスニペットを取り入れてもらいたい。
インクルードガードとは
この記事に辿り着いている人にはそもそも説明不要かもしれないが、何かのご縁でこの記事にたどり着いた人の中には「インクルードガードとは何ぞや?」という疑問を持つ人もいるかもしれないので、ここらでその疑問に答えておく。
読者の前提条件を合わせるというのは何においても重要である。
もし知っている場合は次のチャプターまで飛んでもらって構わない。
ただ、簡単な説明しかしないので、もう少し詳しいことが知りたくなった人は「C++ インクルードガード」みたいな感じでググってみてくれ。
掘り出し物の情報も一緒に手に入れられるかもしれない。
話を戻そう。
C/C++ ではソースファイル (.c/.cpp) とヘッダファイル (*.h) を分けて書く必要があるが、いろんなソースファイルから同じヘッダファイルを参照したいことはまぁまぁある。
小さなアプリケーションの場合はメインとなるソースファイルがすべてをインクルードすれば事足りるかもしれないが、少し規模が大きくなってきて複数のソースファイルからインクルードが必要になった場合、「多重インクルード」という問題が発生しはじめる。
とても端折って説明すると、多重インクルードとは、要はとある定義が複数回行われることによってコンパイラが定義を一意のものとして解釈できなくなってしまうことである。
また、ヘッダファイルの相互参照を行っている場合は永遠にお互いを参照し続けるという無限ループにも陥ってしまう。
上記 2 点以外は問題がなかったとしても、コンパイラさんにとってそんなことは知ったこっちゃない。
書かれたことを基に自動かつ高速にやってくれるだけなので、書かれたこと自体に問題があった場合、それはあなたの責任なのだ。
ではその問題を回避するにはどうすればよいのだろうか?
そんなときに必要になるのが、今回のテーマである「インクルードガード」という手法 (お作法?) である。
実際にはこんなコードになるだろう。
#ifndef SOME_HEADER_H_
#define SOME_HEADER_H_
// 何かしらの具体的な宣言や実装コード
#endif /* SOME_HEADER_H_ */
上のコードをインクルードするとどういう動作になるかというと、初めて読み込まれたときは SOME_HEADER_H_
は未定義なので、SOME_HEADER_H_
の定義とともにその下のコードが読み込まれる。
2 回目以降は SOME_HEADER_H_
が定義済みなのでスキップされる。
つまり、インクルードガードを仕込んでおくことで、コンパイル時に定義が複数回行われることがなくなり、上に書いた「多重インクルード」の問題を回避することができる。
あら簡単。
ただ、この記事を読んでいる人の中には「 #pragma once
って書けば終わりじゃん」と思う人もいるかもしれない。
確かにそうだ。
一行で済むから煩わしさもそれほどでもないだろう。
しかしその解決方法には穴がある。
というのも、 #pragma once
は C/C++ の標準機能には含まれていない。
つまり、 Visual Studio ではうまくコンパイルできるがそれ以外の環境では多重インクルードのエラーが出るという状況が起こりうる。
要は使用するコンパイラによっては、インクルードガードしたつもりができていないという現象が起こりうるのだ。
「そんな環境は捨ててしまえ」という答えも状況によっては間違いではないと思う。
しかし、たかがインクルードガードごときでコンパイラの選択肢を狭めてしまうというのは個人的にはお勧めしない。
「面倒くさいから」という理由なら、この後説明するスニペットを使ってもらえれば、その問題は解決するだろう。
スニペット紹介
前置きが長くなってしまったが、ここからがこの記事の本題である。
インクルードガードは毎回書いているととっても煩わしい作業だ。
しかし、ヘッダファイル名を UPPER_SNAKE_CASE のフォーマットで書きたいので、下手なスニペットだと二度手間になってしまう。
今回は大文字区切りの単語ごとにアンダースコア (_) を挿入する形でインクルードガードをファイル名から自動生成するところまでやってくれるスニペットを作成した。
この記事を読んでくれている人の煩わしさも吹き飛んでくれれば、自分としてもうれしい限りである。
VSCode のユーザースニペット登録の仕方は公式サイトで丁寧に解説してくれているので省かせてもらいたい。
C++ のユーザースニペットを登録するところまで開けたら、お好みの場所に以下の文字列をコピペしてもらえればうまくいくはずだ。
"include guard": {
"prefix": "guard",
"body": [
"#ifndef ${TM_FILENAME_BASE/([A-Z][a-z]+$)|((^|[A-Z])[a-z]+)/${1:/upcase}${2:/upcase}${2:+_}/gm}_${TM_FILENAME/^.*\\.([^\\.]*)$/${1:/upcase}/}_",
"#define ${TM_FILENAME_BASE/([A-Z][a-z]+$)|((^|[A-Z])[a-z]+)/${1:/upcase}${2:/upcase}${2:+_}/gm}_${TM_FILENAME/^.*\\.([^\\.]*)$/${1:/upcase}/}_",
"",
"$0",
"",
"#endif /* ${TM_FILENAME_BASE/([A-Z][a-z]+$)|((^|[A-Z])[a-z]+)/${1:/upcase}${2:/upcase}${2:+_}/gm}_${TM_FILENAME/^.*\\.([^\\.]*)$/${1:/upcase}/}_ */",
],
"description": "template for header include guard.",
}
仮に MyHeaderFile.h
というファイルを開いて上記スニペットを実行したとすると、以下のコードが自動生成される。
#ifndef MY_HEADER_FILE_H_
#define MY_HEADER_FILE_H_
/* スニペット実行後のカーソル位置 */
#endif /* MY_HEADER_FILE_H_ */
コメントの書き方は //
始まりがいいとか、冒頭に作成日や作成者を記載したいという場合はお好みで変えてもらって一向に構わない。
そんな制限を設けるつもりは毛頭ないし、そもそも自分が楽できるというのが何よりも便利なカスタマイズなはずだ。
存分にここからカスタマイズしてもらいたいし、もし気が向いたらコメントで知らせてくれると嬉しい。
おわりに
最後まで読んでくれてありがとう。
ここまで読んでくれた心優しい読者のためにもう 1 つだけ Tips を簡単に紹介したい。
上ではインクルードガードのテンプレートのみ生成するスニペットを紹介した。
だが、少し手を加えればクラス宣言や構造体宣言なども一緒にすることができる。
突き詰めれば、ヘッダファイルでの雛形の作成はほとんどスニペットにお任せできる。
そこまで自動生成することができれば、今まで以上に自分の書きたいコードに専念することが可能になるはずだ。
また、浮いた時間を積み重ねれば、コーヒー 1 杯分くらいの時間はいつの間にか捻出できていることだろう。
今まで無心でコーディングしていた時間が少しだけ楽しくなりそうだな!
最後に、この記事に間違った情報が載っている場合は気軽にコメントいただけるとありがたい。
感謝の気持ちを込めて修正に取り組ませてもらう所存である。
それではまたの機会にお会いしよう。
ばいちゃ!