#pragma twice

KAB-studio > プログラミング > #pragma twice > 306 Version 15.06 ヘッダーファイルを2度読み込む?

#pragma twice 306 Version 15.06 ヘッダーファイルを2度読み込む?

前のページへ 表紙・目次へ 次のページへ

 Version 15.06
ヘッダーファイルを2度読み込む?

「前回は、ヘッダーファイルをインクルードする順番について説明しまし
た」
『ちゃんと順番を考えてインクルードしないとダメなのよね……』
「でも、それ以外にやっかいな問題がヘッダーファイルにはあるんです」
『げげ』
「ヘッダーファイルはソースファイルの代わりになるって説明しました」
『単なる置き換えなんだもんね』
「だから、こんなこともできます」

// Sub.h

#include <Windows.h>

『げ、ヘッダーファイルでインクルードしてる!!』
「実際、前回のように〈インクルードする順番〉が関係してくる場合、
ヘッダーファイル内で〈必要なヘッダーファイルをインクルードしておく〉
というのはよく使われるパターンなんです」
『そか、そうすれば順番気にするどころか、 Typedef.h をインクルードし
なきゃって考える必要もないんだ。なんだ便利じゃない』
「でもはたしてそうでしょうか、というのが今回の話」

・循環参照

『じゅんかんさんしょう?』
「簡単に言うと、蛇が自分のしっぽを食べるような感じ」
『ぐるぐるぐるぐる……』
「たとえば Sub.h をこんなふうにしたり」

// Sub.h

#include "Sub.h"

『げ、 Sub.h が Sub.h インクルードしてる! これやるとずーっと
インクルードし続けちゃうわけね』
「実際にはこういうコンパイルエラーになります」

fatal error C1076: 
    コンパイラの制限 : ヒープの領域を使い果たしました; 
    上限を設定するために /Zm オプションを使用してください。

「ヒープっていうのはメモリ領域のこと。コンパイルする時、
ソースファイルやヘッダーファイルをメモリに書き込んでそれをコンパイル
するんだけど、そのメモリが足りなくなったってエラー」
『永遠にインクルードされるんだから当然なるわよねー。でもこれ、絶対あ
り得ないと思うんだけど』
「じゃあこれは? Sub.h はこうで」

// Sub.h

#include "Typedef.h"

「 Typedef.h はこう」

// Typedef.h

#include "Sub.h"

『あー……複数のヘッダーファイルを経由してぐるっと一周……』
「こういうことがあるから、ヘッダーファイルの中で他のヘッダーファイル
をインクルードするときには細心の注意が必要、かな」
『てかこれやっちゃいそう……』
「次は、二重定義」

・複数のヘッダーファイルでの二重定義

「まず Main.cpp がこう」

// Main.cpp
#include <Windows.h>

#include "Typedef.h"
#include "Sub.h"

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    return 0;
}

「次に Typedef.h 」

// Typedef.h

struct DATA
{
    int m_i;
};

『あ、構造体だ、懐かし〜』
「最後に Sub.h 」

// Sub.h

#include "Typedef.h"

『あ、 Typedef.h をインクルードしてる……けど、今回は循環参照じゃな
いよね。なら大丈夫そうだけど』
「全然大丈夫じゃないんです」
『へ? ビルド……あ、エラー』

typedef.h(4) : error C2011: 
    'DATA' : 'struct' で示される型としてすでに定義されています。

「つまり、構造体 DATA がふたつもあるから」
『すでに定義されているのに再定義しようとしたからエラー……』
「これはとても良くあるパターンなんです。ヘッダーファイルから
ヘッダーファイルをインクルードする、ってすると簡単に起きます」
『やっぱりそれって良くないのねー』
「でも実は、 API やランタイムでは、二重に読み込むなんて普通にあるこ
となんです」
『? それってつまり、回避策があるってこと?』
「そういうこと。多分火美ちゃんも見たことあると思うけど」

// Typedef.h

#ifndef _TYPEDEF_H_
#define _TYPEDEF_H_

struct DATA
{
    int m_i;
};

#endif // _TYPEDEF_H_

『あ! 見たことある! この #ifndef 使ったの!!』
「 Stdafx.h とかの、 MFC 使う時に作られるヘッダーファイルは全部こう
なってるし、 API や ランタイム、 MFC のヘッダーファイルもこうなって
るでしょ」
『うん、そういうので見たことある』
「それに、 Version 6.07 ( No.107 ) で」
『そっか、 #ifndef のプリプロセッサ教えてもらったときにやったんだ
ね』
「もう一度簡単に説明すると」

#ifndef _TYPEDEF_H_
// 中身
#endif // _TYPEDEF_H_

「は、 _TYPEDEF_H_ が #define て定義されて〈いない〉場合にのみ
〈中身〉が有効になります。もし _TYPEDEF_H_ が定義されていたら、
〈中身〉は空白に置き換わります」
『なくなっちゃうわけね』
「初めてこのヘッダーファイルがインクルードされた時には、 _TYPEDEF_H_ 
は定義されていないので〈中身〉が有効になります。その中に」

#define _TYPEDEF_H_

「があるので、この時点で _TYPEDEF_H_ が定義されます」
『2度目に読み込まれた時には、 _TYPEDEF_H_ が定義されているから
〈中身〉が空白に置き換えられるから、再定義されない、ってわけね』
「そういうこと。ただ、これはひとつ問題があります」
『問題?』
「 Typedef.h ってファイルが複数あったら?」
『あ……両方とも _TYPEDEF_H_ になっちゃったらまずいね』
「 MFC の場合は特殊なコードを付けて回避しています。自分で作るときに
はライブラリ名とかを頭に付けるといいかも」
『それでもちょっと不安かも』
「そういう場合のために、もっとスマートな方法もあります」

// Typedef.h

#pragma once

struct DATA
{
    int m_i;
};

「 #pragma は特殊な機能を持つプリプロセッサで、 once を指定するとそ
のヘッダーファイルは1回しか読み込まれなくなります」
『でもさ、あまり使われてないよね』
「 #pragma そのものの知名度が低いからね。そうだね、今までのヘッダー
ファイルの仕組みを聞いて、どう思った?」
『なんかすんごくめんどい。インクルードの順番とか循環参照とか、そうい
うの気にしなきゃいけないのが』
「でしょう。実は、他のプログラミング言語では、そういうの気にしなくて
いいんです」
『へ?』
「ヘッダーファイルは〈置き換え〉じゃなくなってるし、インクルードも何
度でもしていいし、順番も関係なし。仕組みそのものがもっとスマートに
なっているんです」
『つまり、 C++ って古くてダメってこと?』
「だめじゃないけど、古いのは確か。だから、こういう手作業で面倒なこと
が必要になってくるし、新しいプログラミング言語に追い付こう、ってこと
で #pragma が追加されたりしたんです」
『あ、 #pragma ってあとから追加されたんだ』
「そう、比較的最近の機能なんです。だからあまり知られてないのかな。
ヘッダーファイルって、単に使う関数のをインクルードすればいい、って
イメージだったかもしれないけど」
『全然違う』
「それを踏まえて使わないと大変、ということで」

/*
    Preview Next Story!
*/
「以上が、ソースファイルやヘッダーファイルまわりの基礎」
『基礎ってことは、これから応用?』
「そう、たとえば DLL とか」
『 DLL !! あの DLL 、 System32 フォルダにコピーしたりする』
「そう、その DLL 」
『その DLL を……?』
「というわけで次回」
< Version 15.07 スタティックリンクライブラリと DLL >
『につづく!』
「もちろん火美ちゃんが作ったり使ったり」
『いや無理それ! 難しすぎ!!』
「まぁ実際の所」
『簡単?』
「やっぱり難しいけど」
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。