2014年5月17日土曜日

C++ ヘッダファイルの記述方法




今日は、ヘッダファイルの書き方について

の話になります。

今までは、短いサンプルということもあり、

一つのソースファイル(.cpp)の中に

クラスの定義を記述していました。

しかし、普通はクラスの定義はヘッダファイル(.h)に

記述し、メンバ関数の定義をソースファイル(.cpp)に記述します。

つまり、画像の例でいうと、Peach.h にこれを書いて、

class PeachBox {
public:
    PeachBox();
    ~PeachBox();
    int GetTotal();
private:
    int m_total;
};

Peach.cpp にはこれを書きます。

PeachBox::PeachBox():
m_total(0)
{
}

PeachBox::~PeachBox()
{
    std::cout << "桃の化粧箱オブジェクトが終了します。" << std::endl;
}

int PeachBox::GetTotal()
{
    return m_total;
}

int main(void)
{
    PeachBox myPeachBox;
    std::cout << "化粧箱の中の桃の数は" << myPeachBox.GetTotal() << "個です。" << std::endl;
    return 0;
}


さて、このようにヘッダファイルとソースファイルを分割したことによって、

例えばFood.hといった他のヘッダファイルでPeach.hをインクルードする、

あるいは Peach.cpp から Food.h をインクルードするといった、

少し規模が大きいプログラムでも便利に使えるようになります。

しかし、色々なファイルで #include を使っていると、結果的には同じヘッダファイルを

2度インクルードしてしまうこともあります。

この場合は、クラスを2度定義することになり、コンパイルエラーになります(-_-;)

試しにPeach.h では Food.h をインクルードしてみます。

#include "Food.h"

class PeachBox {
public:
    PeachBox();
    ~PeachBox();
    int GetTotal();

そして、Peach.cppを、こんな感じに変更します。

#include <iostream>
#include "Food.h"
#include "Peach.h"

PeachBox::PeachBox():
m_total(0)
{
}

実際やってみると、Food.hを2回インクルードしているので、もちろんエラーです(^^;)

ちなみに、Peach.h Peach.cpp は同じディレクトリにあるので

これでコンパイルできています。

# g++ -g -Wall -o Peach Peach.cpp
In file included from Peach.h:1:0,
                 from Peach.cpp:3:
Food.h:4:7: error: redefinition of ‘class Food’
Food.h:4:7: error: previous definition of ‘class Food’

こういう時のために、重複インクルードを防ぐための

#ifndef マクロ というプリプロセッサ命令があります(^^)

プリプロセッサとは、コンパイラに対してコンパイルする前に、

事前処理を行わせるためのプログラムのことです。

言葉だけでは、伝わらないので実際に使ってみます。

Food.h
ifndef __FOOD_H  // もし、__FOOD_Hが未定義なら
#define __FOOD_H  // 最初のインクルードで__FOOD_Hを定義します
             // こうすることで、2回目以降は既に__FOOD_Hが
            // 定義されているので、この内容は
            // インクルードされなくなります(*^^)v
#include <iostream>
#include <string.h>

class Food
{
public:
    static double sm_tax;
    void SetPrice(int price);
    int GetPrice(void);
private:
    int m_price;
};

#endif // __FOOD_H  ←通常、目印としてコメントを記述しておきます

Peach.h
#ifndef __PEACH_H
#define __PEACH_H

#include "Food.h"

class PeachBox {
public:
    PeachBox();
    ~PeachBox();
    int GetTotal();
private:
    int m_total;
};

#endif // __PEACH_H

Peach.cpp
#include <iostream>
#include "Food.h"
#include "Peach.h"

PeachBox::PeachBox():
m_total(12)
{
}

PeachBox::~PeachBox()
{
    std::cout << "桃の化粧箱オブジェクトが終了します。" << std::endl;
}

int PeachBox::GetTotal()
{
    return m_total;
}

int main(void)
{
    PeachBox myPeachBox;
    std::cout << "化粧箱の中の桃の数は" << myPeachBox.GetTotal() << "個です。" << std::endl;
    return 0;
}

コンパイルと実行結果
# g++ -g -Wall -o Peach Peach.cpp
# ./Peach
化粧箱の中の桃の数は12個です。
桃の化粧箱オブジェクトが終了します。

2重インクルードしているにもかかわらず、エラーにならず実行できました。

ただ Food.cpp を作成してメンバ関数を定義していないし、

Peach.cpp 内で使ってもいないので、

Food.h をインクルードする必要のない、

例によって全くおもしろみもないプログラムです...(^^;)

ここでは、重複インクルード防止のための方法として、

#ifndef, #define, #endif という構文を見てきました。

プリプロセッサを使いこなして、品質の良いプログラムを造っていきたいものです。


今日の名言
子供には過去も未来もない、だから現在を楽しむ - 大人はとてもそうはいかない。
                               ジャン・ド・ラ・ブリュイエール

明日をはじめる前に今日をすっかり終える。今日と明日との間に眠りの壁を置く。
これをやり遂げるには、節制の心がけが必要だ。
                               ラルフ・ワルド・エマーソン

ある者は過去の記憶を蒸し返して、我と我が身を苛み続ける。
ある者はまだ見ぬ罪におびえて、我と我が身を傷つける。
どちらも愚かきわまることだ。 - 過去はもはや関係がなく、未来はまだ来ぬ・・・。
                               セネカ

時の真の価値を知れ。引っ掴め。押さえつけろ。そしてその瞬間瞬間を楽しめ。
怠けず、だらけず、ぐずぐずするな。今日できることを明日まで伸ばすな。
                               チェスターフィールド卿