今回は、Visual Studio Community 2015では最初からヘッダファイルに記載さている #pragma once について説明します。
このコードは先頭に「#」が付いていることから「プリプロセッサ」だと分かります。
コンパイルする前に処理をしているのですが、何をしているのでしょうか?
前回のプログラムを題材に説明しましょう。
<sample program 161-01>
/* main.h */
#pragma once typedef struct { int hp; int mp; } Player; #include "sub.h" |
<sample program 161-02>
/* main.cpp */
#include "main.h" int main(void) { Player player; player.hp = 100; player.mp = 50; Status(player); return 0; } |
<sample program 161-03>
/* sub.h */
#pragma once #include "main.h" void Status(const Player player); |
<sample program 161-04>
/* sub.cpp */
#include "sub.h" #include <stdio.h> void Status(const Player player) { printf("Status\n"); printf("HP = %d\n", player.hp); printf("MP = %d\n", player.mp); } |
では、main.h と sub.h の #pragma once を削除して main.cpp をコンパイルしてみてください。
error C2371: 'Player': 再定義されています。異なる基本型です。 note: 'Player' の宣言を確認してください
このメッセージが繰り返し表示されています。
内容は、Plaeyr構造体が「再定義」されていると書かれています。
Player構造体の宣言は1箇所でしか書いていませんが、どういうことでしょうか?
これを理解するには、#include部分を展開する必要があります。
<sample program 161-05>
/* main.cpp */
#include "main.h" /* ←これを展開する */
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
このプログラムの #include "main.h" の部分を展開すると↓のようになります。
<sample program 161-06>
/* main.cpp */
typedef struct {
int hp;
int mp;
} Player;
#include "sub.h"
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
見て分かる通り、sub.h をインクルードしていますので、これも展開します。
<sample program 161-07>
/* main.cpp */
typedef struct {
int hp;
int mp;
} Player;
#include "main.h"
void Status(const Player player);
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
前回指摘した通り、Player構造体の宣言後にStatsu関数のプロトタイプ宣言が書かれていますので問題無いように見えます。
しかし、↑のプログラムの #include "main.h" をさらに展開すると問題点が浮き出てきます。
<sample program 161-08>
/* main.cpp */
typedef struct {
int hp;
int mp;
} Player;
typedef struct {
int hp;
int mp;
} Player;
#include "sub.h"
void Status(const Player player);
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
展開後のプログラムを見ると、Player構造体の宣言が2回続いています。
さらに、sub.h も再度インクルードされています。
sub.h を展開することで、さらにmain.hも展開されます。
このままでは、永久に展開されますので、いつまで経ってもコンパイルが終わりません。
これがエラーの原因です。
ヘッダファイルに記述してあった #pragma once を記述すればこのエラーは消えて無くなります。
では #pragma once には何の意味があるのでしょうか?
「once」の意味は「1回」という意味です。
このコードをヘッダファイルの先頭に記述することで、ヘッダファイルのインクルードを1回に制限することが出来るのです。
ということは、前述のプログラムでは2回目の #include "main.h" は無視されます。
<sample program 161-07>
/* main.cpp */
typedef struct {
int hp;
int mp;
} Player;
#include "main.h" /* ←2回目の include は無視される */
void Status(const Player player);
int main(void)
{
Player player;
player.hp = 100;
player.mp = 50;
Status(player);
return 0;
}
|
これが無視されれば、
<sample program 161-09>
/* main.cpp */
typedef struct { int hp; int mp; } Player; void Status(const Player player); int main(void) { Player player; player.hp = 100; player.mp = 50; Status(player); return 0; } |
Player構造体の宣言の後に、Status関数のプロトタイプ宣言が書かれることになり、何の問題も無くなります。
これを「インクルードガード」と言って、ファイル分割においては必須の知識となります。
このように、#pragma once には非常に大きな意味があるのです。
ただし #pragma once では無く別のコードを書いてあるサイトなども多いと思います。
そういったサイトを見ると説明されていると思いますが、こちらでも説明しておきます。
main.h の内容を #pragma once から書き換えてみます。
/* main.h */
#ifndef INCLUDE_MAIN_H #define INCLUDE_MAIN_H typedef struct { int hp; int mp; } Player; #include "sub.h" #endif |
#ifndef は、
if not defined
の略です。
もし、定義されていなかったら
という意味です。
#ifndef INCLUDE_MAIN_H |
というのは、
もし、INCLUDE_MAIN_H という「文字?」が定義されていなかったら、
という意味になります。
定義されていなかったら、次の行に
#define INCLUDE_MAIN_H |
と書かれています。
これは、前に説明した「置き換え」では無く、正に定義するという意味になります。
INCLUDE_MAIN_H
という「名前?」が定義される、というのは意味が分かり難いですが次のように考えます。
#ifndef INCLUDE_MAIN_H |
→ もし、「INCLUDE_MAIN_H」が定義されていなかったら
#define INCLUDE_MAIN_H |
→ 「INCLUDE_MAIN_H」を定義します。
これによって、2回目にこのファイルがインクルードされようとした時に、すでに「INCLUDE_MAIN_H」は定義されていますから、
#ifndef INCLUDE_MAIN_H |
から、
#endif |
までのコードは無視されます。
難しいと思いますが、これを1行で書いたのが、
#pragma once |
なのです。
※Visual Studio Community 2015では、最初から記述がありますので、そのままにしておいてください。
環境によっては、
#ifndef INCLUDE_MAIN_H #define INCLUDE_MAIN_H ・ ・ ・ #endif |
という書き方しか受け付けないコンパイラもあると思いますので、どちらが良いかということでは無いと思います。
皆さんも環境は良く調べておくようにしてください。
※上で記載した「INCLUDE_MAIN_H」という名前はヘッダファイルごとに違う名前を付けなければなりません。
コピペして変更し忘れることが多くありますので、気を付けてください。