#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のトップページをご覧ください。