#ifdef を使う

二重定義(宣言)にならないように #ifdef を使います。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プロジェクトの設定

  1. Object Class やヘッダファイルが増えてくると、ヘッダファイルの中から他のヘッダファイルを取り込んでいたりして、 宣言が二重、三重に定義されてしまう恐れがあります。
    最もこの恐れがあるのは Windows システム自身のヘッダファイルなのですが、こようなときに使われるのが #ifdef(#ifndef) です。
  2. 最初に二重定義のエラーを起こす例を示しましょう。
    Debag Message Object Class のヘッダファイル DBox.h です。
    ここから DebugString Object Class のヘッダファイル(DString.h)を取り込んでいることに注目して下さい。
    // DBox.h  Debag Message Object Class Header
    #include    <windows.h>
    #include    "DString.h"
    
    class DBOX
    {
      public:
        void    DBox(char *chr, int n);
    };
    
  3. DebugString Object Class のヘッダファイル DString.h です。
    こちらは素直に DSTRING Class を定義しています。
    // DString.h  DebugString Object Class Header
    #include    <windows.h>
    
    class DSTRING
    {
      public:
        void    DStr(char *str);
    };
    
  4. Main Program です。
    DBox.h と DString.h を取り込んでいます。
    問題は DBox.h を取り込むと、その中から DString.h が取り込まれているために、二重に取り込まれることです。
    このようなときに「二重定義のエラーメッセージ」が表示されます。
        ...\dstring.h(7) : error C2011: 'DSTRING' : 'class' で示される型としてすでに定義されています。
        cl.exe の実行エラー
        
    実は windows.h も二重に取り込まれているのですが、#ifdef(#ifndef) が使われています。
    /**************************************/
    /*★ #ifdef Test Program    前田 稔 ★*/
    /**************************************/
    #include    "DBox.h"
    #include    "DString.h"
    
    DBOX        DBox;
    DSTRING     DString;
    
    // Windows Main 関数
    int  WINAPI  WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
    {
        DBox.DBox("DMsg Call",0);
        DString.DStr("Debug Message\n");
        return 0;
    }
    
  5. これを防ぐには #ifdef(#ifndef) を使ってヘッダファイルを定義します。
    // DBox.h  Debag Message Object Class Header
    #ifndef _DBOX
    #define _DBOX
    #include    <windows.h>
    #include    "DString.h"
    
    class DBOX
    {
      public:
        void    DBox(char *chr, int n);
    };
    
    #endif
    
    // DString.h  DebugString Object Class Header
    #ifndef _DSTRING
    #define _DSTRING
    #include    <windows.h>
    
    class DSTRING
    {
      public:
        void    DStr(char *str);
    };
    
    #endif
    
  6. .cpp ファイルも示します。実際にコンパイルして確かめて下さい。
    // DBox.cpp  Debag Message Object Class
    #include    "DBox.h"
    
    // Debug Message Box
    void  DBOX::DBox(char *chr, int n)
    {   char    str[80];
        wsprintf(str,"%s=%d[%x]\n",chr,n,n);
        MessageBox(NULL,str,"Debug Message Box",MB_OK);
    }
    
    // DString.cpp  DebugString Object Class
    #include    "DString.h"
    
    void  DSTRING::DStr(char *str)
    {
        OutputDebugString(str);
    }
    
  7. #ifdef に代えて #pragma を使う方法もあります。
    重複して取り込まれる恐れのある DString.h の先頭で #pragma once を定義します。
    ヘッダファイルの中でグローバル領域(int val)を定義してみました。
    コメントを外すと「val の領域が重複して定義されるので」コンパイルエラーになります。
    「グローバル領域や関数本体の定義」には #ifndef や #pragma の効果はありません。
    // DString.h  DebugString Object Class Header
    #pragma     once
    #include    <windows.h>
    #define     SAFE_DELETE(p)  { if (p) { delete (p);     (p)=NULL; } }
    #define     EMSG(x)     MessageBox(NULL,x,"Windows App",MB_OK);
    
    // DBox.h で DString.h を取り込んでいます。
    // Main.cpp からも DString.h を取り込んでいます。
    // 重複して取り込まれるので、この領域は二重定義になります。
    //int     val;
    
    class DSTRING
    {
      public:
        void    DStr(char *str);
    };
    

【メモ】

  1. #ifdef(#ifndef) で使う定義名(_DBOX) に注意して下さい。
    Windows では非常に多くの定義名が使われていて、これとバッティングすると思わぬエラーが起こります。
    例えば _DEBUG は Windows システムで既に使用されています。
    Windows とのバッティングを避ける方法として、定義名を _DSTRING_ のように下線で終わるのが良いかも知れません。
  2. ヘッダファイルの中で「グローバル領域や関数本体」を定義している例を見かけますが、#ifndef や #pragma の効果が無く 「重複定義」の危険性があります。
    ヘッダファイルに定義するのは、次のような宣言です。
  3. グローバル領域や関数本体は、プロジェクト全体で一度だけ定義されるように *.cpp に記述して下さい。

超初心者のプログラム入門(Win32API C++)