Quantcast
Channel: Flat Leon Works
Viewing all 120 articles
Browse latest View live

【C++】短絡評価(ショートサーキット)を知ろう

$
0
0

短絡評価とは

短絡評価とは論理演算子&&||で行われる、式の評価の省略のことです。ショートサーキットとも呼ばれます。

短絡評価は具体的には以下のようなルールで行われます。

  • ||演算では左側の式が、だと判明した時点で、右側の式の評価が省略されます。
  • &&演算では左側の式が、だと判明した時点で、右側の式の評価が省略されます。

短絡評価の例

#include <stdio.h>bool DevilFunction( void )
{
    printf( "I am a devil." );
    int* array = newint[1];
    array[27] = 666;
    return array[1234];
}

int main()
{
    true || DevilFunction();
    false&& DevilFunction();
    return0;
}

関数DevilFunctionはnewしっぱなしだったり、newした領域外に値を書き込んだりと悪いことする関数です。true || DevilFunction();でDevilFunctionを呼んでいるように見えますが、||の短絡評価により、DevilFunction()の式の評価は省略されます。つまりDevilFunction関数は呼ばれていません。同じようにfalse && DevilFunction();も短絡評価によりDevilFunction関数が呼ばれなくなっています。

なぜ短絡評価が存在するのか

なぜ短絡評価が存在するのかというと、||演算の場合、一方がであると判明した時点で、もう一方がどんな値だろうと||演算式の結果はであると確定するからだと思います。&&演算の場合も同様です。つまり無駄な処理を省いているわけです。

短絡評価の使い道

短絡評価は式の評価を省略することができるので、これを利用したちょっとしたテクニックもあります。

ポインタと合わせて使う

ポインタは非NULLかどうかで真偽値になるので、このような書き方をすることができます。

ptr && ptr->IsActive();

この場合ptrがNULLでない場合のみIsActiveメンバ関数が呼ばれるようになります。このとき呼び出すメンバ関数の戻り値はbool(またはboolへ変換できる型)である必要があります。

軽い処理を先に持ってくる

||演算や&&演算の中で軽い処理と重い処理がある場合、先に(左側に)軽い処理を持ってくることで、重い処理をなるべく行わないようにすることができます。

lightWork() && heavyWork();

ただし、lightWork関数がほとんどの場合で真を返すときは、ほとんどの場合で省略されないことになるのであまり意味がありません。

短絡評価の注意点

短絡評価のまさに式の評価が省略される点について注意が必要です。たとえば、その省略された式の中で何か「効果のある処理」つまり「副作用のある処理」を行っていた場合、その効果が発生しなくなります。

#include <stdio.h>#include <string.h>#include <string>

std::string str( "banana orange" );

// 削除に成功したらtrueを返すbool Erase( constchar* p_findStr )
{
    int pos = str.find( p_findStr );
    if ( pos == -1 ){ returnfalse; }
    str.replace( pos, strlen( p_findStr ), "" );
    returntrue;
}

int main()
{
    // appleやbananaが存在する場合削除// そして、両方とも存在していた場合、メッセージを表示if ( Erase( "apple" ) && Erase( "banana" ) )
    {
        printf( "There is no apple and banana.\n" );
    }
    printf( "%s\n", str.c_str() );
    return0;
}

このプログラムは、strに"apple"や"banana"が存在する場合、削除を行います。そして両方とも削除した場合にメッセージを表示します。strは"banana orange"なので、"banana"だけ削除され、最終的にstrは" orange"になることを想定しています。

しかし、実際にはstrは"banana orange"のままになってしまいます。

これは左側のReplace( "apple", "" )で"apple"の削除が行われなかったため、式が偽となり、短絡評価が行われ右側のReplace( "banana", "" )による"banana"の削除が行われなくなってしまったのが原因です。つまり"apple"が存在しないと、"banana"の削除が行われないようになっていたのです。

"apple"の削除、"banana"の削除を両方とも確実に行わせるには以下のようにするべきでしょう。

bool erasedApple = Erase( "apple" );
bool erasedBanana = Erase( "banana" );
if ( erasedApple && erasedBanana )
{
    printf( "There is no apple and banana.\n" );
}

まとめ

短絡評価は||,&&演算子を使う上で必須の知識です。覚えておきましょう。


【C++ 小ネタ】文字列定数いろいろ

$
0
0

文字列定数とは

"foo"←これのことです。文字列リテラルとも呼ばれます。

並んだ文字列定数は自動で連結される

複数並んだ文字列定数は、コンパイル前に1つの文字列定数として連結されます。

constchar* str = "a""b"; // "ab"になるconstchar* str2 = "c"// 間にコメントや改行があっても連結されます。"d"
; // "cd"になる

マクロ関数の引数を文字列化する

マクロ関数の引数は#で文字列化することができます。

#define MACRO( param ) #param

MACRO( aaa ) // "aaa"になる

マクロに関してはこちらもご覧ください。

【C++】プリプロセッサの基礎(マクロ編)

複数行の文字列定数を記述する

文字列定数は途中で改行することができません。以下のような記述はコンパイルエラーになります。

printf( "+----------+\n|          |\n|          |\n|          |\n|          |\n+----------+\n"
);

しかし、並んだ文字列定数は自動で連結されるので以下のように書くことができます。

printf( """+----------+\n""|          |\n""|          |\n""|          |\n""|          |\n""+----------+\n"
);

各行の頭に"、末尾に\n"を付ける必要があります。

もうちょっと楽できないものか…

できました。行末に\n\を置くだけです!

printf( "\+----------+\n\|          |\n\|          |\n\|          |\n\|          |\n\+----------+\n\" );

これは、行末に\を置くと改行が無視されるのを利用しています。

【C++】classとstructの違い

$
0
0

C++にはメンバを持てる存在が3つ存在します。それはclassとstructとunionです。unionはちょっと特殊なので置いておいて、classとstructにはどんな違いがあるのでしょうか。

違いはデフォルトのアクセス指定子のみ

実はclassとstructの違いはデフォルトのアクセス指定がprivateかpublicかだけです。他は全く同じです。

具体的には以下のアクセス指定子です。

class Base{};
class A : Base // アクセス指定をしていないので private継承
{
    int m_Value; // アクセス指定をしていないので private
};
struct B : Base // アクセス指定をしていないので public継承
{
    int m_Value; // アクセス指定をしていないので public
};

classとstructの使い分け

オブジェクト指向におけるクラスとして定義したい場合にclassを。データを格納するだけの構造体として定義したい場合にstructを使うのが一般的だと思います。ですが、ちょっとしたクラスでpublicを指定するのが面倒な場合はstructを使用するのもいいんじゃないかなと思います。

【C++】メンバ関数には必要に応じてconstをつけよう

$
0
0

constメンバ関数

メンバ関数につけるconstとは何か。これです。

class A
{
public:
    int m_Value;
    void Hoge( void ) const// ←このconstです
    {
    }
};

メンバ関数の右側にconstをつけると、そのメンバ関数内ではメンバ変数の変更ができなくなります。このメンバ関数constメンバ関数と呼んだりもします。

constメンバ関数内では、メンバ変数を変更するような他の関数の呼び出しも禁止されます。

void SetZero( int& value )
{
    value = 0;
}

class A
{
public:
    int m_Value;
    void IncValue( void )
    {
        m_Value += 1;
    }
    void Hoge( void ) const
    {
        IncValue(); // メンバ変数を変更する関数なので呼び出し禁止(コンパイルエラー)
        SetZero( m_Value ); // メンバ変数を変更する関数なので呼び出し禁止(コンパイルエラー)
    }
};

挙動的には、constメンバ関数内ではthisはconstのthisポインタになります。つまり、クラスAではthisはconst A*になるというわけです。constポインタはそのメンバ変数の変更と、非constメンバ関数の呼び出しが禁止されます。

A a;
const A* pA = &a;
pA->m_Value = 1; // メンバ変数の変更をしているのでコンパイルエラー
pA->IncValue(); // 非constメンバ関数を呼び出しているのでコンパイルエラー

constメンバ関数のメリット

これの何が嬉しいのか。

  • 意図しないデータ変更を防ぐことができる ->バグの発生を減らす
  • メンバ関数がオブジェクトのデータ変更しないことをすぐに知ることができる ->ソースコードの可読性が上がる

バグの発生を減らし、ソースコードの可読性が上がるconstメンバ関数。使わない手はないです。

constメンバ関数の使いどころ

メンバ関数constにするかどうかは、そのメンバ関数が情報を取得するための関数なのか、情報を変更するための関数なのかですぐに決めることができます。情報を取得するための関数の場合はconstをつけましょう。たまに情報を取得しつつ、情報を変更する関数もあったりしますがその場合はconstはつけないようにします。

constメンバ関数内でもメンバ変数を変更したい場合

constメンバ関数内でメンバ変数を変更するためのmutableというキーワードがあります。これはメンバ変数の宣言時に頭に付けて使います。

class A
{
public:
    mutableint m_Value; // mutableを付けて宣言void IncValue( void )
    {
        m_Value += 1;
    }
    void Hoge( void ) const
    {
        IncValue(); // これは相変わらずダメ。thisはconstポインタのままなので。
        m_Value += 1; // m_Valueはmutable付きで宣言されているので変更可能。
    }
};

どういうときにmutableを使うのかというと、内部的な情報を変更するときに使います。たとえばキャッシュなどです。

class A
{
    constchar* m_pName;
    mutableint m_NameLengthCache;
public:
    A( constchar* p_name  ) : m_pName( p_name ), m_NameLengthCache( -1 ){}

    constchar* GetName( void ) const { return m_pName; }
    void SetName( constchar* p_name  ){ m_pName = p_name; m_NameLengthCache = -1; }
    
    int GetNameLength( void ) const
    {
        if ( m_NameLengthCache == -1 )
        {
            m_NameLengthCache = strlen( m_pName );
        }
        return m_NameLengthCache;
    }
};

クラスAのGetNameLengthメンバ関数内では最初の一回のみ名前の長さを計算してそれをメンバ変数に保存するようにしています。つまりキャッシュです。ここでポイントなのは、m_NameLengthCacheはクラスAの内部的な情報であり、利用者側には関係のない情報だということです。constメンバ関数内でm_NameLengthCacheが変更されたとしても利用者には関係がないので、クラスAのデータは変化していないとみなすことができます。

このようにconstはmutableによって、厳密にデータ変更の有無をあらわすのではなく、利用者から見たデータ変更の有無を表すものとして利用することが可能になっています。ただし、あまり多用すべきではありません。mutableはconstに例外を与えるものなので、多用するとconstの信頼性がなくなっていきます。キャッシュ用途ぐらいにとどめておいたほうがいいと思います。

C++11ではconstメンバ関数はスレッドセーフを保証するという意味も付加されるようになったらしいです[参考 ]。この記事でのキャッシュの例は、C++11以降だとconstメンバ関数の要件を満たさないことになります。注意してください。

constメンバ関数の注意点

constメンバ関数によって保証されるのはメンバ変数が変更されないことです。メンバ変数がポインタや参照の場合、その参照先は自由に変更できてしまいます。

class A
{
    int& m_Other;
    int* m_pOther;
public:
    A( int& other ) : m_Other( other ), m_pOther( &other ){}
    void Hoge( void ) const
    {
        m_Other = 1;
        *m_pOther = 1;
    }
};

constメンバ関数内でm_Other = 1;のようなことができてしまっています。これはメンバ変数の参照先の値を変更しているだけで、メンバ変数自体には変更を加えていないからです。メンバ変数の参照先もクラスAのデータであるとみなしたい場合は困る挙動です。これは注意するしかないです。

また、ポインタを外部に公開する場合も注意が必要です。

class A
{
    int& m_Other;
    int* m_pOther;
public:
    A( int& other ) : m_Other( other ), m_pOther( &other ){}
    int* GetOther( void ) const { return m_pOther; }
};

GetOtherメンバ関数にはconstがついていますが、その戻り値のポインタの中身を変更することができてしまいます。これもメンバ変数の参照先もクラスAのデータであるとみなしたい場合には困る挙動です。しかしこの場合にはよい対策があります。

class A
{
    int& m_Other;
    int* m_pOther;
public:
    A( int& other ) : m_Other( other ), m_pOther( &other ){}
    constint* GetOther( void ) const { return m_pOther; }
};

const付きにして返せばよいのです。こうすることでm_pOtherが指す先もクラスAのデータとみなすような挙動にすることができます。つまりAがconstの場合、m_pOtherが指す先もconstにすることができるのです。このconstメンバ関数でポインタをconstポインタにして返す方法はとてもよく使われる手法です。

constメンバ関数オーバーロード

同名のメンバ関数constありなしでオーバーロードすることができます。

class A
{
    int& m_Other;
    int* m_pOther;
public:
    A( int& other ) : m_Other( other ), m_pOther( &other ){}

    constint* GetOther( void ) const { return m_pOther; }
          int* GetOther( void )       { return m_pOther; } // constなしでオーバーロード
};

これを利用することで、オブジェクトがconstかどうかで返す値もconstかどうかが変わるゲッター関数を作ることができます。

int value;
A a( value );
A* pA = &a;
const A* pA2 = &a;
*(pA->GetOther()) =0; // pAは非constポインタなのでGetOtherの返却値も非constポインタになる(参照先の中身を変更できる)/*  エラー*(pA2->GetOther()) =0; // pA2はconstポインタなのでGetOtherの返却値もconstポインタになる(参照先の中身を変更できない)*/

また、このゲッター関数をconst版のみを公開するようにすれば、取得のみ可能で変更不可なゲッターにすることができます。 このconstメンバ関数オーバーロードを利用したゲッター関数の定義は、先ほどの「ポインタをconstポインタにして返す手法」と合わせてよく使われる手法です。覚えておきましょう。

constメンバ関数を使うかどうかでソースコード全部に影響がでる

constメンバ関数は絶対に使うべき存在ではあるのですが、もし今まで使っていなくてこれから使おうと思っているのであれば今すぐ使いはじめるべきです。なぜならconstメンバ関数は部分的に使うということができず、一部で使い始めるとすべてのソースコードconstに対応する必要がでてくるからです。つまり使い始めるのが遅れれば遅れるほどconst対応が大変になります。今すぐ使い始めましょう。

【C++ イディオム】メソッドチェイン + 引数のクラス化 = 名前付きパラメータイディオム

$
0
0

メソッドチェインと引数のクラス化

メソッドチェイン」はメソッド(メンバ関数)を連続で呼び出す手法です。「引数のクラス化」は増えてしまった引数をクラス化して、1個のクラスにカプセル化する手法です。この2つは独立したプログラミングの手法ですが、一緒に使うことで「名前付きパラメータ」イディオムと呼ばれる便利な手法が生まれます。まずは「メソッドチェイン」と「引数のクラス化」について紹介したいと思います。

メソッドチェインとは

a.foo().bar().baz()のようにメソッド(メンバ関数)呼び出しを連続で行うことです。これはメンバ関数*thisを返すことで実現します。

class A
{
public:
    A& foo(){ /*何か処理*/return *this; }
    A& bar(){ /*何か処理*/return *this; }
    A& baz(){ /*何か処理*/return *this; }
};

メソッドチェインを利用することで、1つの式として複数メンバ関数を呼び出すことができます。この「1つの式として」というのがポイントです。通常、複数メンバ関数を呼び出すには呼び出す回数だけ文(statement)が必要になります。

// メソッドチェインを利用しない場合
A a;
a.foo(); // 文
a.bar(); // 文
a.baz(); // 文

メソッドチェインを利用すると複数メンバ関数呼び出しを1つの式として行うことができます。

// メソッドチェインを利用しない場合
A a;
a.foo().bar().baz(); // 全体で1つの式になる

1つの式なのでこういう書き方もできます。

func( A().foo().bar().baz() );

引数のクラス化

「引数のクラス化」とは複数の引数を1つのクラスにまとめて、関数の引数をそのクラス1つだけにすることです。「引数のクラス化」という用語があるわけではないのですが、この記事ではこの手法のことを「引数のクラス化」と呼ぶことにします。そしてそのクラスをパラメータクラスと呼ぶことにします。

int func( int a = 1, int b = 2, int c = 3 ){ return a + b + c; }

// ↓引数のクラス化class FuncParams
{
public:
    FuncParams(){
        // パラメータのデフォルト値をセット
        a = 1;
        b = 2;
        c = 3;
    }
    int a;
    int b;
    int c;

    // 値のセットを楽にするユーティリティ関数void SetSameValue( int value )
    {
        a = value;
        b = value;
        c = value;
    }
    // プリセットで値をセットする関数void Preset1( void )
    {
        a = 10;
        b = 9;
        c = 8;
    }
};

int func( const FuncParams& params ){ return params.a + params.b + params.c; }

このように複数の引数がパラメータクラス1つのみになります。この手法のメリットはたくさんあります。

  • 見やすくなる
  • 関数の引数の増減への対応が簡単になる( メンバ変数の数を変えるだけ )
  • 引数をセットする順番が自由になる
  • メンバ変数名でセットすることになるので可読性が上がる
  • デフォルト値を自由に設定できる( パラメータクラスのコンストラクタでメンバ変数に値をセットしておくとそれがデフォルト値になる )
  • メンバ関数を用意しておけば、値のセットを楽にしたりプリセットを用意したりといったことが可能になる

パラメータクラスを使った関数呼び出しは以下のようになります。

int main() {
    FuncParams params;
    params.b = 5;
    params.a = 2;
    func( params );
    return0;
}

引数のクラス化とメソッドチェインを組み合わせる

このパラメータクラスにメソッドチェインを組み合わせるとさらにエレガントな書き方が可能になります。

先ほどのパラメータクラスをメソッドチェインに対応させてみましょう。

class FuncParams
{
public:
    FuncParams(){
        // パラメータのデフォルト値をセット
        a = 1;
        b = 2;
        c = 3;
    }
    int a;
    int b;
    int c;
    FuncParams& SetA( int value ) { a = value; return *this; }
    FuncParams& SetB( int value ) { b = value; return *this; }
    FuncParams& SetC( int value ) { c = value; return *this; }
};

すると関数呼び出しを以下のようにすることができます。

int main() {
    // メソッドチェイン対応前
    FuncParams params;
    params.b = 5;
    params.a = 2;
    func( params );

    // メソッドチェイン対応後
    func( FuncParams().SetB( 5 ).SetA( 2 ) );
    return0;
}

すっきりと1行で関数呼び出しができるようになりました。

一番最初の引数のクラス化をする前とも比較してみましょう。

int main() {
    // 普通の関数呼び出し
    func( 2, 5 );

    // 名前付きパラメータイディオムによる関数呼び出し
    func( FuncParams().SetB( 5 ).SetA( 2 ) );
    return0;
}

パラメータクラスを利用することで、SetB、SetAなどどの値にセットしているのかがわかりやすくなっています。さらに、セットする順番も自由になっています。

この手法は「名前付きパラメータイディオム」と呼ばれるようです。参考:More C++ Idioms/Named Parameter - Wikibooks, open books for an open world

まとめ

名前付きパラメータイディオムはいいぞ。

【C++ ゲームプログラミング】STLで実装する最小のタスクシステム

$
0
0

タスクシステムとは

タスクシステムとはゲームプログラミングで利用されるプログラムの設計のことです。ゲームプログラムは更新処理と描画処理を繰り返すことで動作します。この更新処理と描画処理を小さな単位(タスク)にすることで実装しやすくしようというのがタスクシステムの根本のアイデアだと思います。

タスクシステムに必要な最小の機能は以下のとおりです。

  • タスクリストの管理(保持/追加/削除)
  • 各タスクの更新処理と描画処理の呼び出し
  • タスクの生成と破棄の仕組み
  • タスクに具体的な実装を与える仕組み

C++の場合、それぞれ以下のように実装するのが一番シンプルな方法でしょう。

  • タスクリストの管理(保持/追加/削除)
    • std::list
  • 各タスクの更新処理と描画処理の呼び出し
    • std::listを走査して呼び出す
  • タスクの生成と破棄の仕組み
    • new/deleteを利用する
  • タスクに具体的な実装を与える仕組み
    • 継承と仮想関数を利用する

実装例

#include <stdio.h>#include <list>#include <algorithm>class Task
{
public:
    virtual ~Task(){}
    virtualvoid Update( void ){}
    virtualvoid Draw( void ){}
};

class TaskController
{
    std::list<Task*> m_TaskList;
public:
    ~TaskController()
    {
        for ( std::list<Task*>::iterator it = m_TaskList.begin(); it != m_TaskList.end(); ++it )
        {
            Task* p_task = *it;
            delete p_task;
        }
    }
    void AddTask( Task* p_task )
    {
        m_TaskList.push_back( p_task );
    }
    void EraseTask( Task* p_task )
    {
        std::list<Task*>::iterator it = std::find( m_TaskList.begin(), m_TaskList.end(), p_task );
        if ( it != m_TaskList.end() )
        {
            delete *it;
            m_TaskList.erase( it );
        }
    }
    void Update( void )
    {
        for ( std::list<Task*>::iterator it = m_TaskList.begin(); it != m_TaskList.end(); ++it )
        {
            Task* p_task = *it;
            p_task->Update();
        }
    }
    void Draw( void )
    {
        for ( std::list<Task*>::iterator it = m_TaskList.begin(); it != m_TaskList.end(); ++it )
        {
            Task* p_task = *it;
            p_task->Draw();
        }
    }
};

class TaskPlayer : public Task
{
    int m_AttackWait;
public:
    TaskPlayer() : m_AttackWait( 0 ){}
    virtualvoid Update( void )
    {
        m_AttackWait++;
        m_AttackWait %= 3;
    }
    virtualvoid Draw( void )
    {
        if ( m_AttackWait == 0 )
        {
            printf( "Player:Attack\n" );
        }
    }
};

class TaskEnemy : public Task
{
    int m_AttackWait;
public:
    TaskEnemy() : m_AttackWait( 0 ){}
    virtualvoid Update( void )
    {
        m_AttackWait++;
        m_AttackWait %= 10;
    }
    virtualvoid Draw( void )
    {
        if ( m_AttackWait == 0 )
        {
            printf( "Enemy:Attack\n" );
        }
    }
};

int main()
{
    TaskController taskController;
    taskController.AddTask( new TaskPlayer );
    taskController.AddTask( new TaskEnemy );
    for ( int i=0; i<30; ++i )
    {
        printf( "Turn:%d\n", i+1 );
        taskController.Update();
        taskController.Draw();
    }
    return0;
}

※このコードはいろいろと安全性の配慮に欠けています。例えば、AddTaskにすでに追加済みのポインタやNULLポインタが入ってくることが考慮されていません。

まとめ

C言語とは違いC++では簡単にタスクシステムを構築することができます。クラス、継承、仮想関数、STLの勉強にもなるので自分で作ってみることをおすすめします。

【次回予告】「タスクシステムからの脱却を考える」

【C++ ゲームプログラミング】タスクシステムからの脱却を考える

$
0
0

タスクシステムのメリット

タスクシステムのメリットは、ゲームの構成要素が作りやすくなりそして扱いやすくなる点です。

  • 作りやすくなる : 更新処理と描画処理のみ実装すればよい
  • 登場/退場させやすくなる : タスクを追加、削除するだけ
  • 勝手に動いてくれる : 勝手にタスクの更新/描画処理を呼んでくれる

ゲームの画面内にはたくさんのオブジェクトがあります。それらは各々で動いたり動かなかったりそれぞれ別の動作を行います。そしてよく現れよく消えます。このゲーム内のオブジェクトを実装するのにタスクシステムはとても合致しています。

タスクシステムの問題点

タスクシステムは便利なものですが問題点もあります。

  • 更新/描画の順番を制御できない
  • 特定のタスクを探すのに向いていない(全部のタスクを個別にチェックする必要がある)
  • プログラムの設計がタスクシステムに囚われがちになる
    • 別のシステムを作成すべきなのにタスクシステム内に実装してしまう( シーン管理タスク、衝突管理タスクなど )
  • これらに起因する設計とパファーマンスの悪さ

タスクシステムのタスクはシンプルな考え方なので使いやすいのですが、シンプルゆえに細かな制御が難しいという点があります。汎用的すぎるとも言えます。 また、タスクが便利なゆえに、すべてをタスクで済ませようとしていまいがちです。

タスクシステム不要論

そもそもタスクシステムって必要でしょうか。確かにタスクシステムがあれば簡単にゲーム内の表示物を実装することができます。ですがこの仕組は、C++ならstd::listと仮想関数で簡単に実現できます[参考]。ならば、もっと具体的なシステムを用意したほうが良いのではないでしょうか。

例えばタスクシステムではなく、ゲーム空間を表すゲームワールドというクラスを作ります。ゲームワールドクラスはキャラクターリスト(プレイヤー、敵)、カメラ、地形コリジョン、背景データ(マップチップなど)を持ちます。ゲームワールドの更新処理では、キャラクター、カメラの更新処理、キャラクターへの重力の加算処理、地形との衝突処理、キャラクター同士の衝突処理などを順番に行います。ゲームワールドの描画処理では、カメラ位置を設定し、背景データから背景の描画、キャラクターの描画を行います。デバッグ機能として地形コリジョンを表示する機能を持たせてもいいかもしれません。

タスクシステムという汎用的なシステムをやめゲームワールドという具体的なシステムを作ったことで、処理の順番が明確になり、具体的なデータへのアクセスも簡単になっています。プログラムの見通しがよくなったと思いませんか?

タスクシステムが生まれた背景

タスクシステムは昔から伝わる手法です。昔というのがポイントです。昔はC言語でプログラミングが行われていたので、std::listも仮想関数も無かった上にメモリ資源(とCPU資源)が厳しかったというのがタスクシステムが利用された理由だと思います。メモリ資源が厳しいので、各タスクは固定長のメモリが用意されそれを使い回すことで、高速なメモリ割り当てを実現していました(らしい)。そしてこれらを実装したタスクシステムは、std::listと仮想関数の組み合わせによる実装よりずっと手間がかかるものなので、必要に応じて具体的なシステム(ゲームワールドのような)を用意するのが大変だったんだと思います。

逆に言うと、現代ではstd::listも仮想関数もある上にメモリも潤沢にあるのでタスクシステムにこだわる必要はないということです。

タスクシステムがまだ有効な場面

タスクシステム不要論を説明しましたが、それでもタスクシステムが有効な場面というのはあります。

  • 小規模なゲーム
  • 気合と根性がある場合
  • タスクシステムを大局的に使う場合

小規模なゲーム

小規模なゲームではタスクシステムで十分戦えるかもしれません。

気合と根性がある場合

気合と根性があればどんなに大規模なゲームでもタスクシステムでやっていけると思います。意外と、なんとかなるものです。

タスクシステムを大局的に使う場合

今まで話してきたタスクシステムは描画物の管理としてのタスクシステムでした。実はタスクシステムには全体的なプログラムの管理としての使い方もあります。ゲームプログラムが毎フレーム行うべきことはキャラクターの更新だけではありません。例えば、ゲームパッドからの入力を集計したり、UIを更新したり、シーン管理を行うなどです。これらをタスクにしておけば、ゲームプログラム全体の毎フレームの流れを制御することができます。基本的にこれらの処理はどのゲームでも決まった順番で行われるのでタスクシステムにせずソースコードに直接書いてもいいのですが、タスクシステムにしておくと新しい機能を追加しやすくなります。例えば、処理時間を計測するプロファイルタスクを作り任意の場所に追加することで任意の場所に処理時間計測を追加することができます。

ただし、このような用途の場合はタスクシステムではなくモジュールシステムとでも呼んだほうがいいような気がします。

まとめ

  • 現代ではタスクシステム的なものはstd::listと仮想関数で簡単に実現でき、メモリ資源にも余裕があるのでタスクシステムにこだわる必要はない
  • 具体的なシステムを作ることでプログラムの見通しがよくなる
  • ただし小規模なゲームはタスクシステムで十分かもしれない
  • タスクシステムには大局的な使い方もある

【次回予告】タスクシステムの派生系

【C++ ゲームプログラミング】タスクシステムの発展形

$
0
0

前回「タスクシステムからの脱却を考える」という記事を書きました。この記事では「タスクシステムではなく具体的なシステムを作ろう」と述べましたが、同時に「タスクシステムには大局的な使い方もある」ということも述べました。今回はそれを踏まえてタスクシステムをより便利にする方法考えたいと思います。

タスクが親子関係を持てるようにする

タスクが親子関係を持てるようになると、タスクのグループ化が可能になりタスクの扱いがとても楽になります。そして更新処理などの順番もある程度制御できるようになります。 またタスクが親子関係を持てるようにすることはタスクシステムを大局的に使うのに便利です。

タスクの親子関係で座標を相対的にする

タスクの親子関係で座標を相対的にするというのは、つまり親が動けば子も動くといったような機能です。これはタスクシステム側に持たせても良いし、タスクの個別の処理内でやるのでもいいと思います。ただし、タスクシステムを大局的に使うことを考えると、すべてのタスクに座標が必要なわけではないので一部のタスクにのみ座標を持たせる仕組みとそのタスクが座標を持っているかどうかを判断する仕組みが必要になります。(一番素直な実装はPositionTaskみたいな派生クラスを用意し、dynamic_castでPositionTaskかどうかを判定)

イベント伝搬の仕組みを導入する

イベントは主にシステム側から各タスクへの通知の仕組みです。例えば、ゲームが中断された瞬間に何かをさせたい場合は中断イベントクラスを作り、それを送信するようにします。各タスクはイベントを受信し、処理を行ったり行わなかったりします。このときイベントがすべてのタスクに送信されるのではなく、登録制にして一部のタスクにのみ送信されるような設計にする場合もあります。またタスクの親子関係を利用して、親でイベントを止め、子には伝搬しないようにする仕組みを導入することもできます。

ここで言うイベント伝搬の仕組みは、各タスクがイベントごとに仮想関数を用意するようなものではなく、各タスクがイベント受信のための仮想関数を1つだけ持つようなものです。この場合、受信するイベントの種類を判定するためにイベントにIDを持たせたり、ダウンキャストで判定を行うなどの処理が必要になります。

class A : public Task
{
public:
    #if 0 // こうではなくて    virtual void ReceivePauseEvent( PauseEvent* p_event ); // 各タスクがイベントごとに仮想関数を用意するタイプ    #else// こうvirtualvoid ReceiveEvent( Event* p_event ) // 各タスクがイベント受信のための仮想関数を1つだけ持つタイプ
    {
        // イベントの判定が必要になる        #if 0 // IDによる判定        if ( p_event->GetID() == PAUSE_EVENT_ID )        {        }        #else// ダウンキャストによる判定if ( dynamic_cast<PauseEvent*>(p_event) )
        {
        }
        #endif
    }
    #endif
};

このイベント伝搬の仕組みを導入すると、新しい処理の種類が増えた時に、タスク基底クラスに新しく仮想関数を追加する必要がなくなります。また、作っているゲーム特有の処理、例えばマリオで「Pスイッチが踏まれたときに何か処理をさせたい」という場合にも対応することができます。イベント伝搬ではなく処理ごとに仮想関数を用意する仕組みだと、タスク基底クラスにそのPスイッチ用の仮想関数を用意する必要がでてきます。しかしタスク基底クラスにゲーム特有の処理を含ませるのは正しく抽象化できていないことになるのでやってはいけません。

また、イベント伝搬の仕組みを導入すると「更新処理」「描画処理」もイベントの1つとして扱えるようになります。

メッセージ送受信の仕組みを導入する

メッセージ送受信はタスクからタスクへメッセージを送信するための仕組みですが特に決まった設計があるわけではなく、いろいろな設計があります。またイベントと区別されないこともあります。ここではメッセージ送受信の設計の1例として説明したいと思います。

メッセージ送受信は、タスクからタスクへメッセージを送信するための仕組みです。イベントと似ていますが用途が少し違います。イベントは全体への通知です。メッセージはタスクからタスクへの通知です。イベントは登録制の場合、各タスクが「イベントの種類」を指定して「システム」に「自分」を登録します。対してメッセージは、「メッセージの種類」を指定して「送信者」に「自分」を登録します。

メッセージのわかり易い例はUIです。例えばボタンタスクがあったとして、ボタンタスクはクリックされると「クリック」メッセージを送信します。もしこのボタンタスクの「クリック」メッセージに登録者がいれば、そこへメッセージが送信されます。メッセージの受信はイベントの受信とほぼ同じ作りになります。イベントと同じようにメッセージの種類(と場合によっては送信者)を判定する必要があります。

class A : public Task
{
public:
    virtualvoid ReceiveMessage( Message* p_message )
    {
        // イベントの判定が必要になる        #if 0 // IDによる判定        if ( p_message->GetID() == MESSAGE_CLICK_ID )        {        }        #else// ダウンキャストによる判定if ( dynamic_cast<ClickMessage*>(p_message) )
        {
        }
        #endif
    }
};

これは少し不便なので、メッセージ受信登録時にメンバ関数ポインタも渡して呼ばれるメンバ関数を指定するような設計にすることも考えられます。

弱参照の仕組み

各タスクはいつ消えるのかわかりません。生のポインタでタスクを参照していると、破棄済みのタスクにアクセスしてしまう可能性があります。タスクを弱参照できる仕組みがあると便利でしょう。(というか必須?)

弱参照ポインタ(WeakPointer)はC++03には存在しません。Boostのboost::weak_ptrを利用するか、自作する必要があります。もちろんC++11以降のweak_ptrを使う手もあります。弱参照ポインタの仕組み自体は単純なので自作も難しくはありません。

弱参照ポインタの実装例( 適当に書いたのでバグがあるかも )

#include <stdio.h>class Task;
class TaskPointer
{
    friendclass Task;
    struct PointerInfo
    {
        Task* m_pTask;
        int   m_RefCount;
        PointerInfo(){ printf( "new PointerInfo\n" ); }
        ~PointerInfo(){ printf( "delete PointerInfo\n" ); }
    };
    PointerInfo* m_pPointerInfo;
    TaskPointer( Task* p_task )
    {
        m_pPointerInfo = new PointerInfo;
        m_pPointerInfo->m_pTask = p_task;
        m_pPointerInfo->m_RefCount = 1;
    }
public:
    TaskPointer( const TaskPointer& other )
    {
        m_pPointerInfo = other.m_pPointerInfo;
        if ( m_pPointerInfo ){ m_pPointerInfo->m_RefCount += 1; }
    }
    ~TaskPointer()
    {
        --m_pPointerInfo->m_RefCount;
        if ( m_pPointerInfo->m_RefCount == 0 ){ delete m_pPointerInfo; }
    }
    Task* GetPointer( void )
    {
        return m_pPointerInfo ? m_pPointerInfo->m_pTask : NULL;
    }
    void SetNull( void )
    {
        if ( m_pPointerInfo )
        {
            m_pPointerInfo->m_pTask = NULL;
        }
    }
};

class Task
{
    TaskPointer m_TaskPointer;
public:
    Task() : m_TaskPointer( this ) {}
    virtual ~Task() { m_TaskPointer.SetNull(); }
    TaskPointer GetTaskPointer( void ) { return m_TaskPointer; }
};

int main() {
    Task* p_task = new Task;
    TaskPointer p = p_task->GetTaskPointer();
    printf( "%s\n", p.GetPointer() ? "Exists" : "Not exists" );
    delete p_task;
    printf( "%s\n", p.GetPointer() ? "Exists" : "Not exists" );
    return0;
}

別の方法としてタスクにIDやハンドルなど持たせて、それを使ってそのタスクが生きているかどうかを判定して生きている場合のみポインタにアクセスするという方法もあります。ただ、弱参照ポインタの方が楽だと思います。

タスクシステムという名前も発展させる

タスクシステムを発展させる方法を見てきましたが、実はこれらは他のシステムで見られるものです。

親子関係と親子関係による相対座標の機能が付いたシステムはゲームエンジンでよく見かけ、シーングラフと呼ばれたりします。またGUIフレームワークWidgetに親子関係を持たせることができ、親子関係による相対座標になっていたりします。イベントやメッセージの仕組みはGUIプログラミングには必須なのでたいてい存在します。

なので、タスクシステムに親子関係やイベント伝搬やメッセージ送受信の仕組みを入れた場合、それをタスクシステムと呼び続けるのは少し違和感があります。なんて呼べばいいのかは思いつきませんが…。フレームワークとか?少なくともTaskクラスはObjectクラスとかの方があっている気がします。

まとめ

タスクシステムを発展させるとフレームワークとして扱うこともできるようになる。


オブジェクト指向とは何なのか考えてみた

$
0
0

オブジェクト指向とは何なのか考え抜いてみました。

オブジェクト指向の概要

用語の位置づけプログラミング(プログラム設計)の手法
用語の意味オブジェクトへのメッセージ送信でプログラミング(プログラム設計)を行うこと
なぜ使われるのか1.手続き型プログラミングより自然な考え方になるから
2.プログラミング言語の機能によるサポート次第でより便利になるから
3.設計の工夫で良質なプログラミングが可能になるから

オブジェクト指向の解説がわかりにくくなっているのは、「オブジェクト指向自体の解説」と「オブジェクト指向をより便利にする言語機能の解説」と「オブジェクト指向でよりよいプログラミングを行うための手法の解説」がごっちゃになって行われているからだと思います。オブジェクト指向自体は「オブジェクトへのメッセージ送信でプログラミング(プログラム設計)を行うこと」でしかありません。

オブジェクト指向の位置づけ

オブジェクト指向の位置づけをまとめてみました。

プログラミングの手法
命令型プログラミング宣言型プログラミング
手続き型プログラミング関数型プログラミング論理プログラミング制約プログラミング
オブジェクト指向プログラミング

上から下に派生していると考えてください。 ここで注目すべきは、オブジェクト指向は命令型、そして手続き型の流れを汲んでいるということです。(※宣言型の方はあまり詳しくないので間違っているかもしれません)

命令型から手続き型へ、そしてオブジェクト指向

プログラミングの一番シンプルな手法は命令型プログラミングです。命令型プログラミングはプログラムを命令の羅列と捉えます。それを発展させたのが手続き型プログラミングです。手続き型プログラミングの「手続き」とは命令の羅列のかたまりのことです。手続きを呼び出すという手法によってより良質なプログラミングが可能になりました。そしてオブジェクト指向はそれをさらに発展させたものと考えることができます。オブジェクト指向における「メッセージ」は「対象付きの手続き」とと捉えることが出来ます。

オブジェクト指向+αでメリットが出てくる

手続き型プログラミングはそれ自体ではちょっと便利くらいな手法です。しかしそこに構造化プログラミングの考え方を取り入れることでより良質なプログラミングが可能になりました。同じように、オブジェクト指向もそれ自体のメリットはプログラムを人間が理解しやすくなった程度のものです(これだけでも相当大きなメリットですが)。そしてそこにオブジェクト指向を元にしたプログラミング言語の機能や設計手法を取り入れることで、より良質なプログラミングが可能になっていきます。

そして、一般的にオブジェクト指向の解説でよく行われるのはその「オブジェクト指向を元にしたプログラミング言語の機能や設計手法」の部分です。それらは良質なプログラミングに必要なものですが、オブジェクト指向の一部と捉えると混乱してしまうかもしれません。いや、実際にもうそれらは世間一般的にはオブジェクト指向なのかもしれませんが、理解が難しいのであれば一旦それらをオブジェクト指向+αの部分であると意識すると理解しやすくなるかもしれません。

(おまけ)良質なプログラミングとは何か

良質なプログラミングとは、実装、変更、拡張がしやすいプログラムまたは設計のことです(持論です)。実装しやすいというのはバグが生まれにくいというのも含まれます。オブジェクト指向に限らず、プログラミングに関するさまざまな手法や機能などは、この「良質なプログラミング」のために存在しています。よくわからない概念や機能が出てきたときは、それによってどのように「良質なプログラミング」に繋がるのかを考えることと、それらの概念や機能の存在意義を理解することが出来ます。

まとめ

オブジェクト指向プログラミングでのクラス分けのコツは役割分担

$
0
0

役割分担でクラス化していく

プログラムを書いていきソースコードが増えるに従って、プログラムの複雑さはどんどん増していきます。プログラムが複雑さが増すと、機能の実装や修正や追加が行いにくくなっていきます。そしてこの状態が悪化していくとスパゲティコードと呼ばれる手に負えない状態になってしまいます。そうならないためにプログラムをクラスや関数に分けていくのですが、このときの分け方の目安になるのが「役割分担」です。

役割分担とはどういうことなのかというと、クラスを役割の担当者に見立てるということです。「○○係にする」というふうに考えてもいいかもしれません。

class Enemy
{
    int m_MaxHP;     // 最大HPint m_CurrentHP;// 現在のHPpublic:
    int GetMaxHP( void ) const { return m_MaxHP; }
    int GetCurrentHP( void ) const { return m_CurrentHP; }
    void AddDamage( int value ) { m_CurrentHP -= value; NormalizeHP(); ]void RecoverDamage( int value ) { m_CurrentHP += value; NormalizeHP(); ]void SetMaxHP( int value ) { m_MaxHP = value; NormalizeHP(); }
    void NormalizeHP( void ) { if ( m_CurrentHP < 0 ) { m_CurrentHP = 0; } elseif ( m_CurrentHP > m_MaxHP ) { m_CurrentHP = m_MaxHP; } }
};

例えば、この敵(Enemy)クラス。HP関係の処理コードをHPクラスというHPの担当者に任せるようにすることで敵クラスを見やすくすることができます。

class HP
{
    int m_MaxHP;     // 最大HPint m_CurrentHP;// 現在のHPpublic:
    int GetMaxHP( void ) const { return m_MaxHP; }
    int GetCurrentHP( void ) const { return m_CurrentHP; }
    void AddDamage( int value ) { m_CurrentHP -= value; NormalizeHP(); ]void RecoverDamage( int value ) { m_CurrentHP += value; NormalizeHP(); ]void SetMaxHP( int value ) { m_MaxHP = value; NormalizeHP(); }
    void NormalizeHP( void ) { if ( m_CurrentHP < 0 ) { m_CurrentHP = 0; } elseif ( m_CurrentHP > m_MaxHP ) { m_CurrentHP = m_MaxHP; } }
};

class Enemy
{
    HP m_HP;
public:
    const HP& GetHP( void ) const { return m_HP; }
};

役割を決めることで実装すべきものと実装すべきでないものが明確になる

クラス化で重要なのはしっかりと役割を決めることです。役割をしっかりと決めることで、そのクラスに何を実装すべきで何を実装すべきでないのかが明確になります。

例えば、敵のHPが半分以下の場合に攻撃力が上がる仕様だったとして以下のように実装したとします。

class HP
{
    int m_MaxHP;     // 最大HPint m_CurrentHP;// 現在のHPpublic:
    int GetMaxHP( void ) const { return m_MaxHP; }
    int GetCurrentHP( void ) const { return m_CurrentHP; }
    void AddDamage( int value ) { m_CurrentHP -= value; NormalizeHP(); ]void RecoverDamage( int value ) { m_CurrentHP += value; NormalizeHP(); ]void SetMaxHP( int value ) { m_MaxHP = value; NormalizeHP(); }
    void NormalizeHP( void ) { if ( m_CurrentHP < 0 ) { m_CurrentHP = 0; } elseif ( m_CurrentHP > GetMaxHP() ) { m_CurrentHP = GetMaxHP(); } }
    bool IsEnemyAttackUp( void ) const { return m_CurrentHP <= m_MaxHP * 0.5; }
};

これは良くない実装方法です。何が良くないかというと、HPクラスはHPの値を管理するだけの役割のクラスなのに、敵の攻撃力があがるかどうかというメソッドを持ってしまっているところです。確かにHPが関係していますが、HPクラスは敵や攻撃力について関知するべきではありません。

クラス名が役割を表すことになる

クラス化は役割を決めることが重要と説明しました。そして役割を正しくクラス名にすることもまた重要です。なぜならソースコード中では役割はクラス名としてしか出現しないからです。クラス定義時にコメントで一緒に役割を記述することもできますが、基本的にクラスはクラス名で役割を表します。

先ほどのHPクラスのHPが半分以下の場合に攻撃力が上がる仕様は、もしクラス名がEnemyHPというような名前だったのならば、クラスの役割は敵のHPを表すものということになるのでIsEnemyAttackUpというメソッドがあっても問題無いのかもしれません。ここらへんの判断は正解がないので個人の感覚で違いがでるところだと思います。

動作も役割分担の対象になる

役割分担は動作もその対象になります。例えば、検索ボックスのような1行のみ入力可能なテキストボックスクラスがあったとします。そしてこのテキストボックスへは数値のみ入力可能という仕様だったとします。このとき「数値のみかどうか入力チェックを行う」という動作も役割分担によってクラス化することができます。オブジェクト指向というとオブジェクト=モノと捉えてしまいがちですが、「モノ」だけではなく「動作」などの目に見えないものもクラス化していくことが大事です。

ちなみに「数値のみかどうか入力チェックを行う」クラスはデータを持つ必要がありません。なぜなら入力値のみで数値がどうかチェックすることができるからです。ではクラス化しなくても関数でよいのではないかと思うかもしれませんが、クラスにしておくことで役割の担当者として融通がきく存在になります。例えばあとから、「範囲内の数値かどうか入力チェックを行う」という機能を追加したくなった場合に、クラスならメンバ変数を持てるので対応できます。

役割を詳細に決めすぎる必要はない

先ほどの入力チェックのクラス化の例は、もし最初の「数値のみかどうか入力チェックを行う」という役割をそのままクラス名にしてNumberOnlyValidatorなどとしていた場合、「範囲内の数値かどうか入力チェックを行う」という機能を追加するとクラス名と動作が一致しないことになってしまいます。一方、クラス名を役割そのままではなく少し曖昧にNumberValidatorとしていた場合、そのクラスの役割は「数値に関する入力チェックを行う」ということになりクラス名を変えること無く「範囲内の数値かどうか入力チェックを行う」機能を追加することができます。

このように役割を最初から詳細に決定しておくのではなく、ある程度ぼかしていた方が融通が利くクラスになります。役割をしっかりと決めることは重要ですが、詳細に決めすぎる必要もないということです。

役割分担でクラス化するかどうかは複雑さで考える

すべての役割をクラス化する必要はありません。例えば、HPクラスの例では、最大HPと現在HPを管理していますが、これを「最大HPの管理する」クラスと「現在HPの管理する」クラスというふうにさらにクラス化する必要はありません。HPクラスは現状では、それほど複雑な実装にはなっていないからです。

役割分担によるクラス分けのメリット

  • クラスに何を実装すべきか実装すべきでないのかがわかりやすくなる
  • クラスが役割に対してのみ責任を持つことになるので、クラスの動作がわかりやすくなる
  • 役割でクラスを実装することになるのでクラスが肥大化しない(ただし役割が曖昧だと肥大化してしまう)
  • メソッドがそのクラスの役割に対する仕事の依頼になるので、クラス間の関係が疎結合になる
  • 細部から全体まですべてこの考え方でクラス分けが可能

役割分担ではクラス分けできない場合もある

役割分担によるクラス分けが適用できない場合が2つあります。1つは基底クラスを作るとき。もう1つは派生クラスを作るときです。これらの場合のクラス分けは役割分担ではなく、抽象化/具体化で考える必要があります。例えば、少し前に出たHPクラスの例で、新たに敵のMPを管理するクラスが必要になったとします。このとき、HPクラスと同じようにMPクラスを作ると似たようなコードがたくさんあることに気づくと思います。こういうときに行うクラス分けが抽象化です。抽象化とはわかりやすく言えば、分類や区分を見つけるということです。HPクラスとMPクラスはともに戦闘用のパラメータという分類になります。なので戦闘用パラメータクラスを作ってそれをHPクラス、MPクラスが継承するようにすることで重複したコードをなくすことが出来ます。

抽象化/具体化の例

役割分担でクラス化するのか抽象化/具体化でクラス化するのかは、どういう目的でクラス分けをしたいのかで決まります。コードの複雑さを解消したい場合は役割分担でクラス分けを行います。コードの重複を解消したい場合は抽象化でクラス分けをします。また、抽象化によるクラス分けを行う場面はコードの重複の解消の他にもう1つあります。具体化を他にまかせる場合です。この場合正確に言うと抽象化ではなく具体化を想定した作りにしておくということです。例えば、ゲームプログラミングに登場するタスクシステムのタスクは具体化を想定したクラスです。タスクの具体的な役割を利用者にまかせていると言えます。

まとめ

役割分担でクラス分けを行うという手法はシンプルに聞こえますがとても強力な方法です。クラス分けの目安が分からない方は、この方法を試してみてはいかがでしょうか。

各種プログラミング言語の動作確認に便利なIdeone

$
0
0

プログラミング言語の動作が気になったときに

プログラミングをしていて、プログラミング言語の動作を確認したくなったことはありませんか。例えば、C++複数の変数を一度に初期化したいと考えたとします。そして確かこんな書き方ができたはずと、int a, b = 7;のような書き方を思いついたとします。しかしこれが期待通りに動くのか不安です。そういうときに役に立つのがIdeoneです。

Ideoneとは

Ideoneは様々なプログラミング言語をブラウザ上で実行できるWebサービスです。

画面はこんな感じになっています。

C、C++C#PerlRubyPythonJavaJavaScriptのような有名どころはもちろん、マイナーな言語もかなりの数対応しています。

Ideoneを使う

先ほどのint a, b = 7;をIdeoneで試してみます。まずはプログラミング言語JavaになっているのでそれをC++にします。言語リストにC++がいくつかありますが、C++03で試したい場合はC++C++5.1、C++4.3.2のどれかを選びます。それぞれコンパイラ(またはそのバージョン)が違います。C++11以降の機能を有効にして試したい場合はC++14を選びます。

コードを入力したら、「Run」ボタンを押します。するとページが切り替わり実行結果画面になります。

実行結果画面

実行結果画面ではプログラムのビルドに成功したのかどうかと、標準出力の結果が表示されます。

このコードの出力結果は0, 7になりました。int a, b = 7;という書き方では最初の変数aが初期化されないようですね。Ideoneで記述したソースコードを変更するにはforkを選びます。editでもいいのですが、editだと出力が見づらいと個人的に感じるのでforkの方をよく使っています。

先ほどのコードをint a =7, b = 7;のような書き方に変更して、実行すると出力結果は7, 7になりました。複数の変数を初期化するにはこのように記述する必要があることがわかりました。

動作確認だけでなく、コードの共有にも便利

Ideoneはちょっとした動作確認だけではなく、Twitterやブログなどでのコードの共有にも便利です。Ideoneに記述したコードを共有するにはIdeoneの実行結果画面のURLを使います。先ほどの実行結果はこのURL「http://ideone.com/cax3Y4」から見ることができます。このようにIdeoneの実行結果は誰でも見れるようになっているので、公開されたらまずいものは記述しないようにしましょう。ただし、設定で非公開にすることもできるようです。ちなみにこのURLはいつまで有効なのかというと、IdeoneのFAQによると「永遠に」とのことです。

また、実行結果画面にはIdeoneのページを埋め込むためのHTMLコードが画面右側に表示されます。例えばこんなコードです。

<scriptsrc="http://ideone.com/e.js/cax3Y4"type="text/javascript"></script>

これをhtmlに埋め込むことで、ブログの記事などにも使うことができます。

↓埋め込むとこんな感じになります

(おまけ)Ideoneの対応言語一覧

※IdeoneのFAQからコピペしたものです。

類似サービス

Ideoneと同じようなWebサービスの1つにWandboxというものもあります。こちらはC++を実行する際に、コンパイラとしてClang,GCCをバージョンごとに指定でき、Boostも使用できるなど、C++関連の充実度が高いものになっています。

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

プログラミングを学ぶこつ

$
0
0

プログラミングを学ぶこつは「慣れ」と「模索」

プログラミングにはいくつもの壁があります。この壁の多さから挫折してしまうひとが沢山いるようです。自分がプログラミングを学んできて思うのは、プログラミングの学習で重要なのは「慣れ」と「模索」なのではないかということです。

ここでいう「慣れ」とは、

  • 時間をかける
  • 何度も触れる

ということです。

また「模索」は、

  • 試行錯誤
  • 新しいことを学ぶ

ということです。

この「慣れ」「模索」でプログラミングの壁をどうやって乗り越えるのかを説明したいと思います。

【壁1】難しい用語が出てきた

プログラミングの学習をしていると新しい概念や機能が一気に出てきて全く理解できない、難しすぎると感じることがあるかもしれません。しかしこれらの概念や機能も、何回も触れていくうちに段々とわかるようになっていくものです【慣れ】。また実際に使ってみたり、あるいはその本の別の章や、別の本を読むことでも理解できるようになるかもしれません【模索】。

【壁2】文字ばかりの画面を見続けるのはもう嫌だ

プログラミングはPCの画面を見続けることになります。これが結構辛かったするのですが、これも「慣れ」で解決することができます。具体的に言うと体が慣れていきます(笑)。あるいは「模索」をすることで、テキストの背景色を暗くして目に優しくすることで対策したりするのかもしれません。

【壁3】情報量が多すぎる

プログラミングはとにかく情報量が多くなります。

プログラミング言語の情報」は使用するプログラミング言語やその作法の情報です。一般的プログラミング言語は、この情報自体で少なくとも本を1冊は書ける情報量です。「やり方の情報」はやりたいことを実現するための情報です。プログラミングにおいてやりたいことを実現する方法は通常いくつもあり、プログラミング言語の選択、API(ライブラリ)の選択、さらにその中でもどのAPIを使うのか、さらにどこを自分でコーディングするのかどうやってコーディングするのかを決めていかなくてはなりません。「自身が記述したソースコードの情報」も当然プログラミングで扱われる情報でその情報量は作業を進めるうちにどんどん増えていきます。

このような情報量の多さを処理しきれず、また取捨選択の難しさから挫折しそうになってしまうかもしれません。しかしこれも「慣れ」で解決することができます。情報量が多かったとしても、時間をかければすべてを拾うことができます。また新しい情報を得ることに慣れていくと必要な情報だけを選んで取得することもできるようになっていきます。やり方の情報が多すぎる問題は、時間をかけることでどんな選択肢があるのか全体像が見えるようになります。全体像が見えるとどれを選択すべきなのかも選びやすくなります。自身が記述したソースコードの情報量が多くて混乱してしまう場合、何度も見ているうちに理解できるようになっていきますし、プログラミングに慣れてくるとどういう書き方をすると混乱しなくなるのかもわかってきます。

【壁4】なかなか完成しない

プログラミングによるものづくりは、予想外の作業が多く発生するものです。このことでプログラミングに関して予想以上の大変さを感じ挫折しそうになってしまうかもません。しかしこれも「慣れ」です。プログラミングを続けていくうちに「そういうものだ」と慣れていくことでしょう。またプログラムの規模が大きくなるにつれ、プログラムの実装が難しくなっていくかもしれません。これは「模索」することでいろいろな手法を知り、プログラムの規模が大きくなっても破綻しないプログラミングの方法を見つけることができるかもしれません。

まとめ

「慣れ」と「模索」でプログラミングの壁を乗り越える方法を見てきましたが、これはプログラミングを初めて覚えるときに限らず、新しい技術を覚えるときにも使えるものだと思います。プログラミングはいつまでも新しいことを覚える必要があります。そういうときにも「慣れ」と「模索」を意識すると楽に新しい技術を覚えることができるかもしれません。

実はこの記事で一番伝えたかったのは、どんな技術習得も「慣れ」が重要なのではないかということです。「慣れ」とは時間をかけることで体が慣れること、あるいは問題を時間が解決してくれるということです。例えば、絵がうまくなりたいとします。そして絵を毎日描き続けていれば、必ずそのうちうまく描けるようになると思います。ここで重要なのは、どのような描き方を身につけたのかではなく、時間をかけていくことでうまい描き方を見つけることができたということです。時間が解決してくれるのです。

【C++ アイデア】戻り値をチェックするアサート

$
0
0

アサートの罠

アサートは便利なものですが、油断するとこんなコードを書いてしまうかもしれません。

#include <stdio.h>#include <assert.h>class A
{
    int m_Value;
public:
    A() : m_Value( 0 ){}
    
    // 値をセットします。戻り値はセットする前の値です。int setValue( int value )
    {
        int ret = m_Value;
        m_Value = value;
        return ret;
    }
};

int main() {
    A a;
    assert( a.setValue( 10 ) == 0 );
    return0;
}

このコードはリリースビルド時はa.setValue( 10 )が実行されなくなってしまいます。assertはリリースビルド時(NDEBUG定義時)に無効になるからです。このassertの性質を知っていても意外とやってしまうミスかもしれません。以下のように、assertの外で戻り値を受け取っておく必要があります。

int main() {
    A a;
    int result = a.setValue( 10 );
    assert( result == 0 );
    return0;
}

しかしこれもまだ少し問題があります。リリースビルドだと変数resultは未使用だと警告が出るかもしれないからです。なので以下のように修正します。

int main() {
    A a;
    int result = a.setValue( 10 );
    (void)result; // 未使用警告対策
    assert( result == 0 );
    return0;
}

なんか面倒くさくないですか?そこで今回は戻り値をチェックするためのアサートのアイデアを紹介したいと思います。

戻り値をチェックするアサート

まずは「戻り値をチェックするアサート」の使い方から。

int main() {
    A a;
    RESULT_ASSERT( 0 ) a.setValue( 10 );
    return0;
}

このRESULT_ASSERTマクロは「自身の引数」と「右側の式の評価結果」が同じ値であることを表明(assert)するアサートです。上の例ではa.setValue( 10 )の結果が0であることを表明しています。a.setValue( 10 )の結果が0でなかった場合はプログラムを停止させます。RESULT_ASSERTマクロはリリースビルド時は無効になるので、RESULT_ASSERT( 0 ) a.setValue( 10 );はリリースビルド時にはa.setValue( 10 );になります。

通常のassertと比較してみましょう。

// 通常のassertint result = a.setValue( 10 );
(void)result; // 未使用警告対策
assert( result == 0 );

// RESULT_ASSERT
RESULT_ASSERT( 0 ) a.setValue( 10 );

とてもすっきりしました。

戻り値をチェックするアサートの実装

「戻り値をチェックするアサート」の実装は以下のようになっています。

#ifdef NDEBUG#define RESULT_ASSERT( expectResult ) #else#define RESULT_ASSERT( expectResult ) GetResultAssertHelper( expectResult ) =template< class T >
class ResultAssertHelper
{
public:
    ResultAssertHelper( const T& expectResult ) : m_ExpectResult( expectResult ){}
    ResultAssertHelper& operator=(const T& result )
    {
        assert( m_ExpectResult == result );
        return *this;
    }
    const T&           m_ExpectResult;
};

template< class T >
ResultAssertHelper<T> GetResultAssertHelper( const T& expectResult ){ return ResultAssertHelper<T>( expectResult ); }
#endif

仕組みは単純で、operator=で右側の評価結果を得るようにして、それを使ってassertを行っているだけです。ちなみにoperator=である意味は特にありません。2項演算子ならなんでもいいと思います。このResultAssertHelperの実装ではアサートによる停止時にソースコードの位置などが正しく表示されないので、そこは工夫した方がいいかもしれません。

扱う文字コードに迷ったらUTF-8を選ぼう

$
0
0

文字コードを決める場面

プログラミングにおいて文字コードを考えるときは、APIやファイルフォーマットなどですでに決められた文字コードに自分が合わせる場合がほとんどだと思います。しかし、自分で文字コードを決める場面というのも少なからず存在します。例えば、自作ツールのファイルフォーマットや、自作ライブラリなどでの文字列の内部表現を決める場合などです。英数字のみの場合はASCIIでいいのかもしれません。問題は日本語も扱えるようにする場合です。文字コードは何を選ぶべきでしょうか。日本語といえばやっぱりShiftJIS?内部表現としてよく見かけるUTF-16?この記事で伝えたいのはUTF-8を使いましょうということです。

文字コードに関する整理

UTF8の話をする前に文字コードについて整理したいと思います。まず文字コードとは、文字をバイト表現するための方式のことです。文字コードは「文字集合」と「符号化方式」の2つで成り立っています。「文字集合」とは、その文字コードがどの文字を扱うのかということです。例えば文字コードASCIIの文字集合は基本的に英数字のみで、「あ」のような日本語が含まれません。「符号化方式」は文字集合での位置をバイト列で表すための方式のことです。簡単にいうとデータの圧縮方式みたいなものです。文字集合での位置は「符号点」「符号位置」「コードポイント」とも呼ばれます。

ShiftJIS、EUC-JPはもう古い

ShiftJIS、EUC-JPは捨てましょう。世の中Unicodeです。

Unicodeとは何か

Unicode文字コードの1つ…というか、文字コードの集まりという感じです。Unicodeに含まれる文字コードUTF-8UTF-16UTF-32などがあります。これらが扱う文字集合はすべて同じで、違うのは符号化方式です。なのでこれらの文字コード文字集合Unicodeと呼ぶ場合もあります。Unicodeがすごいのは扱う文字集合が世界中のすべての文字だということです(実際に世界中の文字が収録されているわけではないと思いますが)。そしてUnicodeは現在ではいろいろな場面で使われ、文字コードデファクトスタンダードになりつつあります。

Unicodeの文字数 == 符号位置の数 ではない

Unicodeには文字の結合や異体字セレクタという機能があります。これら自体は文字ではありませんが、符号位置として現れ、直前の文字に作用します。そのためUnicodeでは符号位置の数 == 文字数として扱うことができないようになっています。つまり、Unicodeで文字数を得るには、1文字(符号位置)ずつ調べていく必要があるのです。これはUTF-8UTF-16UTF-32のそれぞれのメリットを考える上で重要なことです。

UTF-8UTF-16UTF-32の違い

Unicodeを使うとして、符号化方式はどれがいいのでしょうか。まずそれぞれの方式の違いを見てみましょう。

UTF-8

UTF-8は、1〜6バイトの可変個のバイト数で1つの符号位置を表します。何バイト使うのかは、先頭1バイトの上位ビットで1が何回連続するのかで決まります。この仕様により、UTF-8はASCIIとの互換性を実現しています。ASCIIで表されたテキストはそのままでUTF-8として解釈をすることが可能になっています。これはUTF-8の大きなメリットです。またUTF-16UTF-32のようにエンディアンの問題も存在しません。

UTF-16

UTF-16は16bitつまり2バイトもしくは4バイトで符号位置を表す方式です。本当は2バイトできっちり文字集合の符号位置を表す予定だったのですが、想定外の文字集合の拡大により2バイトでは足りなくなり、足りない分はUTF-16文字をもう1つ使って表すことになりました(サロゲートペアと呼ぶ)。なのでUTF-16のデータサイズから文字数を得ることはできません。そもそも前述のとおり、結合文字の問題があるので結局は1文字づつ調べる必要があるのですが。UTF-16のメリットとしては、日本語を多く扱う場合、UTF-8よりバイト数が少なくなるということがあります。

UTF-32

UTF-32は32ビットつまり4バイトで文字集合の符号位置を表します。4バイト固定で1つの符号位置を表すためわかりやすいのですが、先ほど言ったようにUnicodeには結合文字などがあるので、UTF-32のデータサイズから文字数を得ることができません。4バイト固定なのでデータサイズも大きくなります。

UTF-8を使うべき理由

UTF-8UTF-16UTF-32の違いを見てきました。あらためてなぜUTF-8を使うべきなのか説明したいと思います。まず重要なのは、結局どの方式も文字数を得るのに1文字ずつ調べる必要があるということです。固定のバイト数のUTF-32も結合文字などの事情から1文字ずつ調べる必要があります。そこはどの方式も同じように面倒だということです。その上でUTF-8にはASCIIと互換性があるという大きなメリットがあります。世の中Unicodeとは言っても英語圏ではASCIIはまだまだ使われています。そのASCIIと互換性があるというのはとても大きなメリットです。またUTF-8にはエンディアンの問題もありません。これがUTF-8を使うべき理由です。

まとめ

特に理由がない場合はUTF-8を使っておきましょう。

【C++】個別に未使用変数の警告を抑制する方法

$
0
0

未使用変数の警告

C++コンパイル時のオプションにより未使用変数の警告が出る場合があります。未使用変数というのはその名の通り、宣言したのに使っていない変数のことです。この未使用変数の警告が一番出やすいのは関数の仮引数の変数です。特に仮想関数では使用しない仮引数というのが現れがちです。この未使用変数の警告が邪魔くさい場合はコンパイラオプションで抑制することもできますが、そうもいかない場合や、一応未使用変数があることを知っておきたい場合は個別に未使用変数の警告を抑制する必要があります。というわけで、この記事では個別に未使用変数の警告を抑制する方法を紹介したいと思います。

※紹介する方法で警告が消えるかどうかはコンパイラによるかもしれません

方法1:仮引数をコメントアウトする

class Task
{
public:
    virtualvoid Update( float/*time*/ )
    {
    }
};

こうするとコンパイラは未使用変数の警告を出さなくなります。ただし、この方法は仮引数にしか使えません。

virtualvoid Update( float )
    {
    }

このようにコメントアウトでなく、仮引数名をまるごと削除してしまっても良いのですが、コメントとして仮引数名を残しておいたほうがコードは読みやすくなると思います。

方法2:voidキャストを行う

void func( void )
{
    bool result = foo();
    (void)result;
}

この方法でも未使用変数の警告を抑制することができます。こちらは仮引数、変数どちらでも使えます。

(おまけ)方法3:voidキャストとコンマ演算子を使う

void func( void )
{
    if ( A* p_a = ((void)p_a,GetA()) )
    {
    }
}

これはvoidキャストの応用です。コンマ演算子を使うことで初期化式内で警告の抑制を行えます。コンマ演算子は、一番右側の式がコンマ演算子の評価結果になります。

こんな書き方しなくても素直に(void)p_a;でいいじゃないかと思われるかもしれませんが、これが便利な場面というのもあったりします。それはそのうち紹介したいと思います。

まとめ

どの方法がいいのかというと方法2のvoidキャストのやり方が一番良いのではないかと思います。仮引数、変数どちらにも使えますし、/**/によるコメントアウトはネストができないので、なるべく使いたくないというのもあります。


ALURE(OpenALユーティリティ)をiOSで使う

$
0
0

クロスプラットフォームなゲーム開発で音を鳴らすにはOpenALが定番っぽいです。MacWindowsiOSAndroidで利用可能だそうで。しかしOpenALだけではwavファイルなどの音声ファイルを扱うことができません。そこで良さげなのがOpenALのユーティリティライブラリであるALUREです。wavファイルはもちろん、Ogg VorbisFLACなどにも対応しています。

しかし、このALUREをiOSで使うための情報が見つからなかったので結構苦労しました。なので記事にまとめておこうと思います。

記事執筆時の環境

iOSOpenAL+ALUREを使う

OpenALの準備

iOSでは最初からOpenALが利用可能なので、XcodeのプロジェクトのLinked Frameworks and LibrariesOpenAL.frameworkを追加するだけで良いです。

ALUREの準備

いろいろ模索した結果、以下の手順になりました。ただし、ビルド周りは詳しくないので不正確な可能性があります。ご注意を。

  • ALURE公式サイト」からソースコード一式を入手
  • ALUREのビルドにはCMakeが必要なので、まだCMakeを入れていない場合は「CMake公式サイト」からバイナリを入手。dmgを展開してアプリをアプリケーションフォルダなどへ置く。ちなみにCMakeはMakefileを生成するためのツールです。
  • https://github.com/cristeab/ios-cmake」からCMakeのiOSツールチェインファイルを入手します。
  • この中のiOS.cmakeの一部を修正します。(私の環境では修正しないとCMakeの実行に失敗しました)

修正前

include (CMakeForceCompiler)
CMAKE_FORCE_C_COMPILER (/usr/bin/gcc Apple)
CMAKE_FORCE_CXX_COMPILER (/usr/bin/g++ Apple)

修正後

set(CMAKE_C_COMPILER "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang")
set(CMAKE_CXX_COMPILER "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++")
  • ターミナルでALUREソースコード一式内のbuildディレクトリに移動して、以下を実行。「iOS.cmakeのファイルパス」は各々で書き換えてください。実行に成功するとMakefileが作られます。
/Applications/CMake.app/Contents/bin/cmake .. -DCMAKE_TOOLCHAIN_FILE=iOS.cmakeのファイルパス
  • makeを実行。libalure-static.aなどが生成されます。
  • このlibalure-static.aを以下のコマンドで中身を展開。(libalure-static.aファイルを直接リンクさせてもリンクエラーになりました)
ar -x libalure-static.a
  • 展開すると以下のような複数.oファイルが生成されます。
    • alure.o
    • buffer.o
    • codec_aiff.o
    • codec_wav.o
    • istream.o
    • stream.o
    • streamdec.o
    • streamplay.o
  • これらをXcodeのプロジェクトのLinked Frameworks and Librariesに追加します。
  • Xcodeのプロジェクト設定でBitcodeを無効にしておく( ライブラリのビルド時にBitcodeを含めていないのでエラーになる )
    • Build Settings > Build Options > Enable BitcodeNoにする
  • alure.hのパスをプロジェクト設定の Build Settings > Search Paths > User Header Search Pathsなどで設定しておく
  • 生成したALUREライブラリのパスをプロジェクト設定のBuild Settings > Search Paths > Library Search Pathsで設定しておく
  • Xcodeでビルドします。ビルドが成功したら成功です。
  • 実機(iPhoneなど)でちゃんと音が鳴るか確認してみる。

(おまけ)手順を見つけるまでの流れ

上記のような手順になった経緯を参考として残しておきます。

Mac用に生成したALUREライブラリをそのままiOSのプロジェクトに持って行っても当然動きません。まず考えたのがALUREのビルドはCMakeを使っているので、CMakeにiOS用の設定があるんじゃないかということです。探してみたところCMake自体はiOSのビルドに対応していませんでしたが、GithubiOS用にビルドするためのCMakeファイルがあるのを見つけました。これhttps://github.com/cristeab/ios-cmakeです。

このファイルを使えばCMake時に以下のようにオプションとして指定することでiOS用ビルドが可能になるとのこと。

cmake .. -DCMAKE_TOOLCHAIN_FILE=../../../toolchain/iOS.cmake -DIOS_PLATFORM=SIMULATOR

試してみたところ、私の環境ではALUREのCMakeを実行するとエラーが発生しました。

CMake Error at CMakeLists.txt:131 (MESSAGE):
  PThreads is required for non-Windows builds!

エラーログ(CMakeError.log)の中身を見ると、ライブラリの存在チェック時になぜかiPhoneSDKではなくMacOSX10.11.sdkなどが使われているようす。ぐぬぬ

いろいろ試してみた結果、iOS.cmakeの以下の場所を修正することでCMakeが成功することがわかりました。

修正前

include (CMakeForceCompiler)
CMAKE_FORCE_C_COMPILER (/usr/bin/gcc Apple)
CMAKE_FORCE_CXX_COMPILER (/usr/bin/g++ Apple)

修正後

set(CMAKE_C_COMPILER "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang")
set(CMAKE_CXX_COMPILER "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++")

この違いがどう違うのかわかりませんが、とにかくこうすることでCMakeが成功するようになりました。ちなみに、CMAKE_FORCE_C_COMPILERからCMAKE_C_COMPILERに変えたのは、CMAKE_FORCE_C_COMPILERが最新のドキュメントで非推奨になっていたからです。https://cmake.org/cmake/help/latest/module/CMakeForceCompiler.html

続いてmakeでビルドします。これは普通に成功し、libalure-static.aが生成されました。

このlibalure-static.aXcodeiOSのプロジェクトのLinked Frameworks and Librariesに追加してビルドします。すると以下のような警告とともにリンクエラーが。

ld: warning: ignoring file ***/Lib/libalure-static.a, file was built for archive which is not the architecture being linked (armv7):

Undefined symbols for architecture armv7:
  "_alureGetErrorString", referenced from:

armv7用のシンボルが見つからないと言われているような。

このリンクエラーの対処法の前に、生成されたlibalure-static.aについて状況を確認したいと思います。

このlibalure-static.aは、内部的には以下のオブジェクトファイルから構成されています。

  • alure.o
  • buffer.o
  • codec_aiff.o
  • codec_wav.o
  • istream.o
  • stream.o
  • streamdec.o
  • streamplay.o

これはarコマンドを使って確認することができます。例:ar -t libalure-static.a

そしてこれらのそれぞれの.oファイルは、armv7armv7sarm64の3つのアーキテクチャのバイナリを合体させたFATバイナリ(universal binary)と呼ばれるものになっています。これはfileコマンドで確認することができます。

$ file alure.o
alure.o: Mach-O universal binary with 3 architectures
alure.o (for architecture armv7):   Mach-O object arm
alure.o (for architecture armv7s):  Mach-O object arm
alure.o (for architecture arm64):   Mach-O 64-bit object

このようにちゃんとarmv7用のバイナリが含まれているのにリンクエラーになってしまっています。

ちなみに、FATバイナリを作るにはlipoコマンドが必要との情報をみかけましたが、ビルドオプションで-arch armv7 -arch armv7s -arch arm64のように-archオプションを複数設定すれば勝手にFATバイナリにしてくれるようです。CMakeではCMAKE_OSX_ARCHITECTURESコマンドで設定したアーキテクチャがビルド時に-archオプションとして渡されるようです。そしてCMAKE_OSX_ARCHITECTURESコマンドはiOS.cmakeによって自動で設定されます。

さて、このリンクエラーを解決するために試しにarコマンドでlibalure-static.aの中身を展開して、それらをLinked Frameworks and Librariesに追加してビルドしてみると…ビルドに成功しました。FATバイナリをアーカイブ化したものはダメなのか、それともアーカイブ化の方法が間違っていたのかよくわかりませんが、とにかくリンクエラーを解決する方法が見つかりました。(解決まで丸2日かかりました)

(おまけ)MacOpenAL+ALUREを使う

ついでにMacでのOpenAL+ALUREの利用方法も紹介しておきます。先にMacでALUREの動作確認をしておくのもいいかもしれません。

OpenALの準備

MacOpenALを使う場合、OpenALは最初から入っているのでビルドオプションで-framework OpenALとするだけで良いです。(もしくはXcodeのプロジェクトのLinked Frameworks and LibrariesOpenAL.frameworkを追加する)

ALUREの準備

ALUREを利用するには以下の手順が必要になります。

  • ALURE公式サイトからソースコード一式を入手
  • ALUREのビルドにはCMakeが必要なので、まだCMakeを入れていない場合はCMake公式サイトからバイナリを入手。dmgを展開してアプリをアプリケーションフォルダなどへ置く。ちなみにCMakeはMakefileを生成するためのツールです。
  • ターミナルでALUREソースコード一式内のbuildディレクトリに移動し、/Applications/CMake.app/Contents/bin/cmake ..を実行。(Makefileが作られる)
  • そのままmakeを実行。(ビルドが走りライブラリファイルが生成される)
  • そのままmake installを実行。(ライブラリファイルなどが/usr/local/libなどに配置される)
  • ビルドオプションで-lalureなどしてリンクする。(もしくはXcodeのプロジェクトのLinked Frameworks and Librariesに追加する)

雑に説明しましたがMacでの利用手順はこんな感じです。

【C++】宣言と定義の早見表

$
0
0

宣言と定義の早見表

宣言だけの書き方は忘れがちなので早見表を作ってみました。

注意 : 一応コンパイルが通ることを確認していますが、もしかしたら間違った書き方をしているかもしれません。

宣言のみ定義(宣言を含む場合あり)備考
グローバル変数extern int a;int a;グローバルスコープ
static変数不可static int a;
ローカル変数不可int a;関数スコープ
関数int func( void );int func( void ){ return 0; }
static関数static int func( void );static int func( void ){ return 0; }定義のstaticは任意
inline関数inline int func( void );
int func2( void );
int func( void ){ return 0; }
inline int func2( void ){ return 0; }
inlineは宣言か定義どちらかにあればよい
関数テンプレートtemplate<class T>
int func( void );
template<class T>
int func( void ){ return 0; }
クラスclass A;class A{};struct、unionも同じ
メンバ変数不可class A {
int a;
};
staticメンバ変数class A {
static int a;
};
int A::a;
メンバ関数class A {
int func( void );
};
int A::func( void ){ return 0; }
仮想関数class A {
virtual int func( void );
};
int A::func( void ){ return 0; }
staticメンバ関数class A {
static int func( void );
};
int A::func( void ){ return 0; }
inlineメンバ関数class A {
inline int func( void );
int func2( void );
};
int A::func( void ){ return 0; }
inline int A::func2( void ){ return 0; }
inlineは宣言か定義どちらかにあればよい
メンバ関数テンプレートclass A {
template<class T>
int func( void );
};
template<class T>
int A::func( void ){ return 0; }
クラステンプレートtemplate<class T>
class A;
template<class T>
class A{};
メンバ関数テンプレート
(クラステンプレート内)
template<class T>
class A{
template<class U>
int func( void );
};
template<class T>
template<class U>
int A<T>::func( void ) { return 0; }
enum不可enum A{};

宣言のみのもの

宣言のみ定義備考
typedef宣言typedef int MyInt;なし
friend宣言 : クラスclass A {
friend class B;
};
なし
friend宣言 : クラステンプレートclass A {
template<class T>
friend class B;
};
なし
friend宣言 : 関数class A {
friend int func( void );
};
なし
friend宣言 : 関数テンプレートclass A {
template<class T>
friend int func( void );
};
なし
friend宣言 : メンバ関数class A {
friend int B::func( void );
};
なし
friend宣言 : メンバ関数テンプレートclass A {
template<class T>
friend int B::func( void );
};
なし
friend宣言 :
クラステンプレートのメンバ関数
class A {
template<class T>
friend int B::func( void );
};
なし
friend宣言 :
クラステンプレートのメンバ関数テンプレート
class A {
template<class T>
template<class U>
friend int B::func( void );
};
なしtemplate<class U>は
記述しなくてもコンパイル成功しました

Ideoneメモ : https://ideone.com/KZoMtG

OpenGLメモ

【Python】個人的逆引きリファレンス

$
0
0

Pythonでの個人的によく書くコードを逆引きリファレンスとしてまとめておこうと思います。

注意:

  • Python2.7を対象としてます
  • Pythonに詳しいわけではないので変な書き方をしているかもしれません
  • 動作確認してません

ファイルの存在チェック

import os.path
if os.path.exists( path ):
    pass

ドキュメント :
os.path.exists

ファイル読み込み

import os.path
if os.path.exists( path ):
    fileData = open( path, 'r' ).read()

もしくは

import os.path
import codecs
if os.path.exists( path ):
    fileData = codecs.open( path, 'r', 'utf_8' ).read()

ドキュメント :
open
codecs.open

ファイル書き込み

openFile = open( outputFile, 'w' )
openFile.write( writeData )
openFile.close()

もしくは

import codecs
openFile = codecs.open( outputFile, 'w', 'utf_8' )
openFile.write( writeData )
openFile.close()

ドキュメント :
file.write

ファイル削除

import os
os.remove( path )

ドキュメント :
os.remove

ディレクトリ削除

import shutil
shutil.rmtree( path )

ドキュメント :
shutil.rmtree

カレントディレクトリの取得

import os
os.getcwd()

ドキュメント :
os.getcwd

カレントディレクトリの変更

import os
os.chdir( path )

ドキュメント :
os.chdir

パスをつなげる

基本的に普通に文字列の+でも問題ないけど、os.path.joinの方が少しだけうまくやってくれる

import os.path
os.path.join( 'aaa', 'bbb' )  # aaa/bbb
os.path.join( 'aaa/', 'bbb' ) # aaa/bbb

ドキュメント :
os.path.join

ファイルパスから絶対パスを取得する

import os.path
os.path.abspath( path )

ドキュメント :
os.path.abspath

ファイルパスからファイル名(拡張子付き)を取得する

import os.path
os.path.basename( path )

ドキュメント :
os.path.basename

ファイルパスからディレクトリ名を取得する

import os.path
dirName = ''if os.path.isdir( path ):
    dirName = os.path.basename( path )
else:
    dirName = os.path.basename( os.path.dirname( path ) )

ファイルパスからディレクトリパスを取得する

import os.path
os.path.dirname( path )

ドキュメント :
os.path.dirname

ファイルパスから拡張子を取得する

import os.path
os.path.splitext( path )[1] # `.txt`など

ドキュメント :
os.path.splitext

ファイルパスがディレクトリを指しているかどうかを取得する

import os.path
os.path.isdir( path )

ドキュメント :
os.path.isdir

外部コマンドを実行

import subprocess
subprocess.call( commandList )

ドキュメント :
subprocess.call

外部コマンドを実行(標準出力は表示しない)

外部コマンドの標準出力を表示しないようにしつつ、標準出力の内容を取得できる

p = subprocess.Popen( commandList, stdout=subprocess.PIPE )
(stdoutStr, stderrStr) = p.communicate()

ドキュメント :
subprocess.Popen

環境変数の取得

import os
os.environ['PATH']

ドキュメント :
os.environ

書式を利用する

"""#include <stdio.h>int main(){{    printf( "{message}" );    return 0;}}""".format( message='aaa' )

ドキュメント :
書式指定文字列の文法

【Python】リストからタプルのリストを作成する

$
0
0

Pythonでリストからタプルのリストを作りたい場合、例えば[1,2,3,4,5,6]というリストがあって、これを[(1,2),(3.4),(5,6)]のようなタプルのリストにしたい場合は以下のように書くのが楽です。

listA = [1,2,3,4,5,6]
listB = zip( listA[::2], listA[1::2] )
print listB # [(1, 2), (3, 4), (5, 6)]

2個ずつのタプルではなく、3個ずつのタプルにしたい場合は以下のようにします。

listA = [1,2,3,4,5,6]
listB = zip( listA[::3], listA[1::3], listA[2::3] )
print listB # [(1, 2, 3), (4, 5, 6)]

スライスのコストが気になる場合は、itertools.isliceを使うのが良いかもしれません。

import itertools
listA = [1,2,3,4,5,6]
listB = zip( itertools.islice( listA, 0, None, 2 ), itertools.islice( listA, 1, None, 2 ) )
print listB # [(1, 2), (3, 4), (5, 6)]

リンク

Python2.7ドキュメント

Viewing all 120 articles
Browse latest View live