ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

【C++】ヘッダファイルとcppファイルの事故らない扱い方(1/2)

書き手:Hidano

C++を勉強していて、クラスを複数使ったプログラムを書く段階になった方を対象に、ヘッダファイルとcppファイルの使い方について解説します。

C++に限らず、プログラミングには「暗黙のルール」というのがいくつもあります。例えば、

  • 変数名の頭文字は小文字にする

  • 変数名に複数の単語を使うときは、2つ目の単語から頭文字を大文字にする

  • bool型の変数、関数の名前は頭に「is」を付ける

などなど、数え上げればキリがありません。

別にこれらのルールを守らなくてもプログラムは動くのですが、ソースコードを読み返したとき、また複数人でコードを共有するときに、こうしたルールを守らないと読みづらくなったり、認識の不一致が起こってしまいます。

そうしたルールの一つに、「同一ファイルに複数のクラスを定義しない」「クラス名とファイル名を一致させる」というものがあります。このルールの意義について説明します。

プロジェクトの規模が大きくなってくると、当然作成するクラスの数も増えていきます。またチームで開発する場合は、自分以外のチームメイトが作ったクラスを利用する場面も頻繁に出てきます。

それらのクラスの仕様(どんなメンバがあり、どのような関数が使えるのかなど)を確認するためには、そのクラスが定義されているヘッダファイル(.hファイル)を見なければいけないのですが、仮に一つのヘッダファイルに複数のクラスが定義されていると、目的のクラスが見つかるまでずっとスクロールをして探さないといけません。

目的のクラス名が分かっているときは開発環境の検索機能(Ctrl+Fなど)を使って検索できますが、目的のクラス名が分からない、漠然と「こういう機能のクラスがあるはず」という情報しか無いときは、探し出すのに無駄な手間が掛かってしまいます。

そんな事態を避けるために、上記の「同一ファイルに複数のクラスを定義しない」「クラス名とファイル名を一致させる」というルールが存在しています。

このルールに則り、例えば「Player」というクラスを作るなら、「Player.h」にクラスの定義を記述し、「Player.cpp」に関数の内容などの実装部を書きます。

ちなみにPlayer.hとPlayer.cppに記述を分ける理由も、チームで開発する場合に読みやすくする必要性からきています。

関数というのは「引数(インプット)」と「内部処理」、「返り値(アウトプット)」の三要素から成ります。

このうち内部処理に関しては、基本的にクラスを作成した責任者だけが知っていれば問題ありません。概要だけ、「この関数はこういう作業をするよ」ということを関数名にして見えるようにすれば良いのです。

引数が必要な場合、どんな値が必要なのかは引数名で教えてあげることができます。関数の概要が示された関数名と、引数の名前だけで、他の開発者がその関数をどのように使えばいいのかを察することができるのが、親切な記述と言えます。

//Player.h
class Player{
public:
    Player();
    ~Player();
    void SetPosition(double x, double y);
private:
    double x, y;
};
//Player.cpp
#include"Player.h"

Player::Player(){
    x = y = 0.0;
}

Player::~Player(){
    
}

void Player::SetPosition(double x, double y){
    this->x = x;
    this->y = y;
}

プレイヤーキャラを生成するPlayerクラスの記述の例を上に示しました。ゲームのジャンルはこだわりませんが、ここでは初代マリオのような横スクロールアクションを作っているとしましょう。

あくまで例なので、パラメータはx,y座標を示すものしかありませんが、実際は他にも歩かせたりジャンプさせたりといった関数が存在することでしょう。

例では座標を設定するSetPosition関数を作成し、引数の名前を「x,y」としてあります。

これで他の人、例えばステージの入口を作っている人がPlayerクラスの仕様を知りたい時は、まずクラスの数だけ存在するヘッダファイルの一覧を見て、プレイヤーキャラに関係ありそうなヘッダファイル名を探します。この作業、1つのファイルから目的のクラスをスクロールして探すよりは格段に楽ですよね。

「Player.h」というまさにドンピシャな名前のヘッダファイルがあったので、それを開くとクラスの仕様が分かります。

外部から使える関数は「public:」の範囲にあるので、「そうか、Playerを入口の前に立たせたい時は、SetPosition関数で入口の座標を指定すればいいんだな」と分かります。この時Player.cppは一切読む必要がないですよね。

ちなみに実際の開発現場では、この例より関数や変数がはるかに多い場合も珍しくないので、そういう時はpublic内だけをチェックして、他は読み飛ばす事もできます。外部の機能を作っている人に関係ある関数はpublic内にしか存在しないはずですからね。

さて、ここまでで「同一ファイルに複数のクラスを定義しない」「クラス名とファイル名を一致させる」ことのメリットは理解できましたでしょうか。

次回はクラスがどんどん増えていった時に発生する参照エラーを回避する方法について解説したいと思います。