C/C++の最近のブログ記事

enumの列挙数って取得できないのかな。

/**
 * ジョイスティック用キーコード
 */
enum E_JOY_CODE
{
    JOY_UP = 0,    ///< ボタン上 ※0始まりを明示
    JOY_DOWN,      ///< ボタン下
    JOY_LEFT,      ///< ボタン左
    JOY_RIGHT,     ///< ボタン右
    JOY_BTN1,      ///< ボタン1
    JOY_BTN2,      ///< ボタン2
    JOY_BTN3,      ///< ボタン3
    JOY_BTN4,      ///< ボタン4
    JOY_BTN5,      ///< ボタン5
    JOY_BTN6,      ///< ボタン6
    JOY_CODE_END,  ///< 最終メンバ(メンバ数取得用)
};

しょうがなく、最初の値を0って明示して、さらに最終列挙値を用意。これでJOY_CODE_ENDを見れば列挙数がわかるって細工したんだけど、他に上手いやり方ないか。

こんな構造体より、

/**
 * ジョイスティックの入力状態
 */
struct ST_JOY_STATE
{
    unsigned char    ucJoyUp;            ///< 上キー
    unsigned char    ucJoyDown;          ///< 下キー
    unsigned char    ucJoyLeft;          ///< 左キー
    unsigned char    ucJoyRight;         ///< 右キー
    unsigned char    aryucJoyBtn[6];     ///< 数字キー
};
ST_JOY_STATE _stJoyState;    ///< ジョイスティックの入力状態

こんな列挙型と配列使ったほうが、

/**
 * ジョイスティック用キーコード
 */
enum E_JOY_CODE
{
    JOY_UP,        ///< ボタン上
    JOY_DOWN,      ///< ボタン下
    JOY_LEFT,      ///< ボタン左
    JOY_RIGHT,     ///< ボタン右
    JOY_BTN1,      ///< ボタン1
    JOY_BTN2,      ///< ボタン2
    JOY_BTN3,      ///< ボタン3
    JOY_BTN4,      ///< ボタン4
    JOY_BTN5,      ///< ボタン5
    JOY_BTN6,      ///< ボタン6
    JOY_CODE_END,  ///< 最終メンバ(メンバ数取得用)
};

unsigned char _aryucJoyState[JOY_CODE_END];    ///< ジョイスティックの入力状態

管理しやすいような気がした。

上のE_JOY_CODE列挙型があれば、ボタンの情報を関連付けやすいし、そんで構造体より配列の方がコードがシンプルになる。

※「///<」はDoxygen用のコメント

Part3 JavaScriptに学ぶ「言語の拡張性」 - ITpro

関数はローワーキャメルケース(先頭が小文字のキャメルケース)になっています。これは,クラス・ベースのオブジェクト指向言語では関数(メソッド)の命名規則がローワーキャメルケースであることに準じています。

オブジェクト指向言語だと、関数名はローワーキャメルケースにするのが一般的なのか。アッパーキャメルケースにしてたわ。直さなきゃ。ひたすら一括置換だ。

if文、for文とかにつける"{"についての話。

C、C++のコーディングだと、改行してから"{"を付ける。スクリプト言語とかWeb系の言語だと、右に"{"を付ける。もちろん規約があればそれに従う。無ければ、一般的な方に合わせる。

右"{"のとき、分岐処理のコメント前に改行するかしないかですごく悩む。

改行"{"だと、

// Aのとき
if (a == x)
{
    FuncA();
    FuncB();
}
// Bのとき
else if (b == x)
{
    FuncC();
    FuncD();
}
// その他
else
{
}

特に問題なし。

これが右"{"になると、

// Aのとき
if (a == x) {
    FuncA();
    FuncB();
// Bのとき           ←この行の密集してる感が嫌だ
} else if (b == x) {
    FuncC();
    FuncD();
// その他
} else {
}

こんな風にごちゃごちゃする。じゃあ、コメント前に改行入れようと。

// Aのとき
if (a == x) {
    FuncA();
    FuncB();

// Bのとき
} else if (b == x) {
    FuncC();
    FuncD();

// その他
} else {
}

でも、コメント前改行ってことにすると、こんなときに↓

// Aのとき
if (x == a) {
    if (y == i) {
        FuncA();
    } else if (y == j) {
        FuncB();
    }
                     ←この不自然な改行が気持ち悪い
// その他
} else {
}

ってことになる。だから、密集してる感が漂ってたら、改行しましょうってことにした。
でも、どういう基準で改行入れてるんだってことになって、、、あー、めんどくせ。。。
自分めんどくせ。

ゲーム処理クラスがDirectX処理クラスを直接使ってるけど、ゲーム処理も、描画エンジンとかに依存せずに独立したプログラムにしたい。ということで、一つクラスをラップすることにした。[]はクラス。

[DirectX Graphics処理]←――[描画]     ←┐
[DirectX Audio処理]  ←――[サウンド再生] ←+―[ゲーム処理]
[DirectInput処理]   ←┬―[入力制御]   ←┘
[Keyboard入力]     ←┘

ゲーム処理クラスはDirectXなんて知らない。だから、描画クラスだけいじれば、ゲームプログラムを一切変更せずに描画エンジンが切り替えられる。

共通処理クラスCCommonっていうクラスを作ってて、その中でエラーログ出力関数みたいなどこでも使うような関数を用意して、いろんなとこで使ってたんだけど、DirectX処理クラスでこの共通処理クラスを使うのは、ちょっと変な気がしてきた。

というのも、DirectX処理クラスは、完全にゲームとは別に独立したものっていう考えで、本来はDLLとして存在するべきものにしてある(DLLにすると逆にめんどいことがあるんで、今は敢えてDLLにしてない)。だから、今作ってるゲームに限らず、このDirectX処理クラスは汎用的に使えるようにしてある。

本来独立して存在するものが、ゲーム処理クラスと同じ共通処理クラスを使用してるのは変。だから、DirectX処理クラスは、共通処理クラスを使わない方がいいな。

そもそも共通処理クラスっていうのが抽象的すぎた。ゲーム処理用の共通クラスとでもしておくか。

共通処理クラスに、デバッグトレースの関数を作った。

/**
 * デバッグトレース
 *
 * @param[in]    a_pszFormat    書式
 */
void CCommon::Trace(const char* a_pszFormat, ...)
{
#ifdef _DEBUG
    va_list    pcParam;         // 引数リスト
    char       szTrace[512];    // 出力文字列

    // 可変引数の取得開始
    va_start(pcParam, a_pszFormat);
    // 出力文字列の作成
    _vsnprintf(szTrace, sizeof(szTrace),
               a_pszFormat, pcParam);
    // 可変引数の取得終了
    va_end(pcParam);

    // デバッグトレース
    OutputDebugString(szTrace);
#endif
}

この関数はエラー発生時とかに使う。

// エラー発生時
if (! bRet)
{
    CCommon::Trace("Errror: %s %s\n",
                   __FUNCTION__, pszErrorMessage);
}

上のフォーマットだと、例えば、

「Errror: CDI::Init DirectInput8オブジェクトの作成に失敗」

って文字列が出力されるんだけど、いろんなとこでこのトレースは使われるわけなんで、出力フォーマットは必ず統一されるようにした方がいい。

それで、直接トレース関数を使うわけじゃなくて、エラー発生用に使うトレース関数を別に作っといた。

/**
 * エラー用デバッグトレース
 *
 * @param[in]    a_pszFunction    関数名
 * @param[in]    a_pszError       エラーメッセージ
 */
void CCommon::TraceOfError(char* a_pszFunction, char* a_pszError)
{
    CCommon::Trace("Errror: %s %s\n",
                   __FUNCTION__, pszErrorMessage);
}

とりあえず、エラー発生した関数名とエラーメッセージだけ出力すればいいかな。ということで、エラー発生時に書く処理はこれ↓。

// エラー発生時
if (! bRet)
{
    CCommon::TraceOfError(__FUNCTION__, pszErrorMessage);
}

ゲームプログラムで使うデバッグトレース、ログ出力、エラーメッセージ表示について。それぞれどういうタイミングで使用するべきか、どういう位置づけで使用するべきか、決めてみた。

[デバッグトレース]

  • ファイル読み込みや、シーンの切り替えなど、重要処理や全体の処理フローがわかるようにトレースする
  • エラー発生時に、そのときの関数の名前や行数などをトレースする

[ログ出力]

  • エラー発生時に、そのときの関数の名前や行数などを、エラーログとして出力する

[エラーメッセージ表示]

  • エラー発生時に、そのことがプレイヤーにわかるよう、メッセージダイアログにて、エラーメッセージを出力する
  • 基本的には、ゲーム起動時に出力する
    例)「DirectXの初期化に失敗しました」
      「画像ファイルの読み込みに失敗しました」

重要度でいくと、

エラーメッセージ表示 > ログ出力 > デバッグトレース

っていう順番かな。

Cで時刻を表すのに、time_t型を使ってるけど、その型が2038年の1月位までしか対応してない。調べてみたら、2038年問題ってのがあった。

2038年問題 - Wikipedia

2038年1月19日3時14分7秒を越えると、この値がオーバーフローし、負と扱われるため、誤作動する可能性が高い。

time_t型のサイズを増やしても、まだ問題があるらしい。

2038年問題と同じように考えると、292277026596年に桁あふれが起こる可能性があり、これを「292277026596年問題」と呼ぶこともある。

292277026596年。ニクニ、ニナナオ、ニムゴクムね。勉強になるぜ。

ファイルから読み込んだバイナリデータを扱うプログラムを改造してんだけど、読み込むファイルのデータがビッグエンディアンになってて、動かす環境はx86系でリトルエンディアンなんで。

1バイトだけのデータとか文字列は別にエンディアンを考慮する必要なし。1バイトずつ見る分にはひっくりかえりようがないから。2バイト以上の整数値は変換しないといけない。

memcpy(&shNum, pFileData, sizeof(short));
memcpy(&lNum, pFileData, sizeof(long));

とかやったら、shNum、lNumは変換必要。そもそも、memcpy関数使わないで、エンディアン変換込みのメモリコピー関数を独自で作ればいいけど。

嫌だったのは、バイナリデータが構造体を表してて、それを取得するところ。構造体は、long型(4バイト)とかのメンバをめちゃくちゃ持ってた。

struct ST_TEST
{
    long    lDataA;
    long    lDataB;
    long    lDataC;
    …
    …果てなく続く(嘘)…
}

この構造体をそのまま

memcpy(&stTestData, pFileData, sizeof(ST_TEST));

ってしたら、↓こんなバイナリデータが、

00 00 00 01  00 00 00 0A  00 00 00 02 …

それぞれ4バイト区切りでひっくり返った値になって解釈されちゃう。↓こんな風に。

lDataA:0x01000000 ※本当は0x00000001というデータで欲しい
lDataB:0x0A000000
lDataC:0x02000000

というわけで、せっかく構造体として存在してるのに、わざわざメンバ一個ずつ見てエンディアン変換かけることに。めんどい。

リトルエンディアンって、よくいう「バッドノウハウ」ってやつになるのかな。

関数内で、do~while (false)を使うのが好き。
使う人は当たり前のように使ってるけど、この書き方が嫌いな人も結構いるっぽい。

メリットは、

  • if文の多用や、それによるインデントの数を防げる
  • 終了時の処理が分散しない(途中returnを無くせる)

ってとこだと思う。

実際↓こんな感じになる。

/**
 * なんかの初期化関数
 *
 * @param[in]    a_nIn    入力値
 *
 * @return    処理結果
 * @retval    true    成功
 * @retval    false   失敗
 */
bool Init(int a_nIn)
{
    bool    bRet = true;        // 戻り値
    char*   pszError = NULL;    // エラーメッセージ

    do
    {
        // パラメータチェック
        if (a_nIn < 0)
        {
            pszError = "パラメータエラー";
            bRet = false;
            break;
        }

        // オブジェクトの生成
        if (! CreateObject())
        {
            pszError = "オブジェクト生成エラー";
            bRet = false;
            break;
        }

        // オブジェクト2の生成
        if (! CreateObject2())
        {
            pszError = "オブジェクト2生成エラー";
            bRet = false;
            break;
        }
    }
    while (false);

    // エラーメッセージ表示
    if (! bRet)
    {
        OutputErrorMessage(pszError);
    }

    // ログ出力とか?必要なら
    OutputLog(__FUNCTION__);

    return bRet;
}

途中returnあると、終了時の処理が分散しちゃって見にくいし、管理しにくい。終了処理は一箇所にまとまってる方がいいかなと。

ちなみに、while文使って、

    while (true)
    {
        // パラメータチェック
        if (a_nIn < 0)
        {
            pszError = "パラメータエラー";
            bRet = false;
            break;
        }

        …(略)…

        break;
    }

でもいいかと思ったけど、会社の人と話したときに、

「breakは忘れるから怖い」

って言ってた。while(true)でbreak忘れて無限ループになるのが怖いと。
do~while文だったら、最後のwhile忘れたらコンパイルがそもそもできないし、その最後のwhileにfalseじゃなくて敢えてtrueを指定するって間違いは無いと思う。
でも、while文のときは、while(true)の中で最後にbreak忘れても、コンパイル通っちゃう。そのbreakだけ忘れるとかだったらやっちゃいそう。

あ、goto文は存在しないことにしときます。

コンストラクタでメンバ変数を初期化する際には、代入じゃなくて、初期化リストを使う。

/**
 * CSampleクラス
 * 
 * @date 2008/04/30
 */
class CSample
{
    int    _nTest;
    int*   _pnTest;
    …

こんなメンバ変数持ったクラスだったら、初期化リスト使った初期化はこうなる。

/**
 * コンストラクタ
 */
CSample::CSample():
    _nTest(0),
    _pnTest(NULL)
{
}

で、メンバ変数が配列とか構造体のときは、どうすんのかと思って調べたけど、見つからなかった。試しに次のメンバ変数を初期化リストに入れてデバッグしてみた。

/**
 * CSampleクラス
 *
 * @date 2008/04/30
 */
class CSample
{
    int        _arynTest[100];    ←配列
    ST_TEST    _stTest;           ←構造体
    …
}

初期値を引数に入れようとするとエラーになったから、空にしてビルドした。

/**
 * コンストラクタ
 */
CSample::CSample():
    _arynTest(),
    _stTest()
{
}

で、このとき構造体"_stTest"は0埋めされてたから、とりあえず構造体は0で初期化されるっぽい。ただ配列"_arynTest"は0埋めとかされてなかった。この方法だと配列は初期化できないみたい。

配列のメンバ変数の初期化は、普通に{}内で、memsetとか使うしかないか。

C++でも自動でドキュメント生成してくれるツールあるに決まってるよなってことで、調べたら、Doxygenっていのが一般的っぽいんで、これを使うことにした。ヘッダコメントとかもDoxygen対応の書式にした。

インストールしたら、DoxywizardってGUIのツールができるから、これ使ってちょっと設定変えて、[Start]ボタン押すだけでドキュメントが生成できた。

コメントの書き方は、QtスタイルとJavaDocスタイルがあるけど、自分はJavaDocスタイルに慣れてるからこれを使うことにした。

設定をいじったとこは、

  • [OUTPUT_LANGUAGE]をJapaneseへ
  • [INPUT_ENCODING]をShift_JISへ
  • privateメンバを表示するように、[EXTRACT_PRIVATE]を有効へ
  • コメント1行目の概要を表示するように、[JAVADOC_AUTOBRIEF]を有効へ
  • ファイル一覧でフルパスが表示されないように、[FULL_PATH_NAMES]を無効へ

ってとこかな。

で、ヘッダコメントは↓こんな感じにした。

  • ファイルヘッダ

/**
 * @file Di.h
 *
 * @brief DirectInput処理
 *
 * @author 1122
 * @date 2008/01/18
 */

  • クラスヘッダ

/**
 * DirectInput処理
 *
 * @author 1122
 * @date 2008/01/18
 */

  • 関数ヘッダ

/**
 * DirectInput初期化
 * 
 * @param[in] a_hWnd ウィンドウハンドル
 * @param[in] a_hInstance インスタンスハンドル
 * 
 * @return 処理結果
 * @retval true 成功
 * @retval false 失敗
 */

前にC++で作ってたゲームが久々気になり、ソースコードを眺めてた。そしたらそのコードにイライラしてきたので、リファクタリングを始めた。

学生の頃に作ったコードだから、もちろんコーディング規約を作ってるわけはなく、暗黙の了解という感じでコーディングの仕方をそろえてた。それで何となくは規則性のあるコードが書かれてたけど、ただ規則的なだけで、わかりにくい記述がいくつかあった。そんで、まずは規約を作ってみた。

関数のヘッダコメントも規約として決めた。
↓例えばこんなん。

//----------------------------------------------------------
// Name   : Init
// Desc   : 初期化
//
// Arg    : [in]  int  a_nIn   : 入力値
//          [out] int* a_pnOut : 出力値
//
// Return : int nRet : 戻り値
//----------------------------------------------------------

引数のとこを"Arg"にするか"Param"にするかというどーでもいいことで悩んだ。自分としては"Param"って書いた方がパッと見わかりやすい。ただね、今回作った規約では、変数名はハンガリアン記法ってことにしてある。ハンガリアン記法だと、引数のプレフィクスは"a_"が一般的。で、この"a_"っつーのは、Argumentの頭文字。そうすると、引数の説明は"Arg"にしないと統一感ねーよな…、となり、散々悩んで、"Arg"に決定。こういうとこでも優柔不断全開。

JavaDocとかphpDocumentorみたいに、C++ Docとかいうのがあればなぁ。。。

ハンガリアンなんだけど、これは元々嫌いだったんだけど、仕事で使ってみたら、コード理解しやすいってことがわかり、なるべく使うようにしてる。

プロフィール

  • 1983年生まれ。
    C++、PHP、JavaScript、ActionScriptで何か作ってます。

技術書

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス (C++ in‐depth series)
JavaScript 第5版
まるごとJavaScript & Ajax ! Vol.1
CakePHPガイドブック
CakePHP ポケットリファレンス (Pocket Reference)
Powered by Movable Type 4.01

このアーカイブについて

このページには、過去に書かれたブログ記事のうちC/C++カテゴリに属しているものが含まれています。

前のカテゴリはActionScriptです。

次のカテゴリはCakePHPです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

2008年10月

      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

リンク