Posts Tagged C++

c++でのファイル読み込みテスト

ゲームプログラマになる前に覚えておきたい技術」を読んでいて、C++のファイル読み込みをやっていた。
Cの時でさえ、そんなにファイルいじったりしていなかったので、ちょっと自分でも書いてみる。
まぁ、写経的なコードになっちゃうんだけれども。

main.cpp

#include <iostream>
using namespace std;
bool readFile( char** buff, int* size, const char* filename );
int main()
{
char *fBuff = 0;
int fSize = 0;
if ( readFile( &fBuff, &fSize, "test_dat.txt" ) ) {
cout.write( fBuff, fSize );
}
delete[] fBuff;
return 0;
}

fileRead.cpp

#include <fstream>
using namespace std;
bool readFile( char** buff, int* size, const char* filename )
{
// 初期化
*buff = 0;
*size = 0;
// ファイルストリームをインスタンス化しつつストリームオープン
ifstream ifs( filename, ios::binary );
// 失敗
if ( !ifs ) { return false; }
ifs.seekg( 0, ifstream::end ); // ファイルストリームを最後まで移動
// 現在の読み込み位置(詰まるところサイズ)を取得
// tellg()はpos_type型を返すので、実体はintでも一応キャスト
*size = static_cast< int >( ifs.tellg() );
ifs.seekg( 0, ifstream::beg );	// ファイルストリームを先頭へ
// メモリ確保
*buff = new char [ *size ];
// ファイルの内容をメモリに読み込む
ifs.read( *buff, *size );
return true;
}

test_dat.txt

Hello.
And say goodbye.

で、結果。

% g++ -o test main.cpp fileRead.cpp
% ./test
Hello.
And say goodbye.

ちなみに、

delete[] *fBuff;

とても、

delete[] &fBuff;

としても怒られた。
ダブルポインタは大本の変数名だけ指定すれば良いんですかい?
メモリリークとかしないのか?
この前紹介した「OpenGLで作るiPhone SDKゲームプログラミング」では

ParticleSystem::~ParticleSystem()
{
int i;
for (i = 0; i < this->amount; ++i) {
delete this->particle[i];
}
delete this->particle;
}

なんてコードがあったから、面倒くさいけど個別に解放しないとリークしちゃうのかと思った。
そうか、その配列内でさらにnewしているから個別に解放しているのか。
そうじゃない場合はそのまま解放して良い、と。
ポインタをもっとよく勉強せねば。。。

そんだけ。

Post to Twitter

, , ,

No Comments

「OpenGLで作るiPhone SDKゲームプログラミング」が面白かった

OpenGLで作るiPhone SDKゲームプログラミング

著者/訳者:横江 宗太(株式会社パンカク)

出版社:インプレス( 2009-12-18 )

定価:

Amazon価格:¥ 7,735

単行本 ( 352 ページ )

ISBN-10 : 4844328085

ISBN-13 : 9784844328087


一言で言うと、この本がとても面白かった。
内容は非常にストレートで、「iPhone向けに簡単なレースゲームを作る」という趣旨の本。
1章がOpenGL ES 1.0を使った2Dの取り扱い。画面への描画。
2章がその応用で「はえたたきゲーム」を作る。

はえたたきゲーム


3章が”パーティクルシステム”と呼ばれる煙などの表現に使われる演出の実装。

パーティクルシステム


4章が「2Dレースゲーム」を作る。

2Dレースゲーム


5章が”衝突判定”の実装。

衝突判定の実装と画面調整


6章が全ての章を応用して「3Dレースゲーム」を作る。

3Dレースゲーム

まず、Objective-CとC++の知識が最低限求められる。
あと、最終章の3Dレースゲームと言ってもそれほどのものを期待してはいけない。
ただ、ゲームの骨組みを組む方法や、画面描画と操作の連携などを学ぶことが出来る。
俺みたいな脳たりんには丁度良い内容となった。

基本的なゲームロジックはC++で実装されているため、そのままではつまらないのでObjective-Cで実装することにした。
これがC++のコードとの対比が出来てなかなか面白かった。
実際問題、C++を前提に書いてあるので付け焼き刃な俺のObjective-C知識ではなかなか無駄な処理が出まくっているのはわかっているのだけれども、それでもやって良かったと思う。
書店で見かけたら、ちょっと目を通してみるのもいいかもしれない。
これだけでゲームは作れないけど、本当に良いきっかけを作ってくれると思う。

ちなみに、上記のゲーム画像はアセットこそお借りしたものの、ソースコードは写経+Objective-Cで書き直したものをiPhone 3G上で実際に動かしたもののスクリーンショット。
一応この程度のものは出来る。

また、Windows用になってしまうけれど、この本の冒頭に書いてある参考文献である「ゲームプログラマになる前に覚えておきたい技術」をこの本の次に読むといい気がしてきた。
実は「ゲームプログラマになる前に~」も持ってはいるものの、なかなか読み進められないでいた。
けど、今回の「OpenGLで作る~」を読んでからなんとなく進められるようになってきた。
基本的に本に書かれているコードは執筆時のいずれかの段階のコードなので、実は動かないものも多いけど、ソースコードに当たれば問題ないレベル。
徐々にステップアップして行くには良いかなぁ、と自分に言い聞かせつつ読み進めてまふ。
参考までに。

ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚

出版社:秀和システム( 2008-11-14 )

定価:

Amazon価格:¥ 4,860

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


そんだけ。

Post to Twitter

, , , , ,

No Comments

C++でのクラスと構造体

前回C++のクラスは構造体の拡張であるという話し。
まず一般的な構造体から見ていきましょう、と。

#include <iostream>
#include <string.h>
using namespace std;
struct hoge
{
int a;
double b;
char c[128];
} foo;
int main()
{
hoge* pFoo = &foo;
foo.a = 100;
foo.b = 3.141592674;
strcpy(foo.c, "構造体のテストじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
cout << "\n\n";
pFoo->a = 500;
pFoo->b = 1.05;
strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
return 0;
}

まぁ、これは普通に読めること前提。
一応実行結果はこれ。

直接参照:
foo.a = 100
foo.b = 3.14159
foo.c = 構造体のテストじゃ~。
間接参照:
pFoo->a = 100
pFoo->b = 3.14159
pFoo->c = 構造体のテストじゃ~。
直接参照:
foo.a = 500
foo.b = 1.05
foo.c = ポインタから更新じゃじゃ~。
間接参照:
pFoo->a = 500
pFoo->b = 1.05
pFoo->c = ポインタから更新じゃじゃ~。

で、これをそっくりクラスで書くと下記の通り。

#include <iostream>
#include <string.h>
using namespace std;
class hoge
{
public:
int a;
double b;
char c[128];
} foo;
int main()
{
hoge* pFoo = &foo;
foo.a = 100;
foo.b = 3.141592674;
strcpy(foo.c, "クラスのテストじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
cout << "\n\n";
pFoo->a = 500;
pFoo->b = 1.05;
strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");
cout << "直接参照:\n" <<
"foo.a = " << foo.a <<
"\nfoo.b = " << foo.b <<
"\nfoo.c = " << foo.c << endl;
cout << "間接参照:\n" <<
"pFoo->a = " << pFoo->a <<
"\npFoo->b = " << pFoo->b <<
"\npFoo->c = " << pFoo->c << endl;
return 0;
}

こんな風にpublic:をつけるだけ。
感がいい人はわかってると思うけど、構造体でもprivate:を付けてしまえばクラスっぽくなると言うね。
となると、構造体でもメンバ関数(メソッド)を用意することができる。

同じようなコードにすると下記なような感じになる。

#include <iostream>
#include <string.h>
using namespace std;
class CFoo
{
private:
int a;
double b;
char c[128];
public:
CFoo():a(0),b(0.0)
{
for (int i = 0; i < 128; ++i)
c[i] = '\0';
}
~CFoo() {}
int setA(int num)
{
a = num;
return 0;
}
int setB(double num)
{
b = num;
return 0;
}
int setC(char* szStr)
{
if (strlen(szStr) > 127) {
return 1;
}
strcpy(c, szStr);
return 0;
}
int getA() { return a; }
double getB() { return b; }
char* getC() { return c; }
};
struct Bar_t
{
private:
int a;
double b;
char c[128];
public:
Bar_t():a(0),b(0.0)
{
for (int i = 0; i < 128; ++i)
c[i] = '\0';
}
~Bar_t() {}
int setA(int num)
{
a = num;
return 0;
}
int setB(double num)
{
b = num;
return 0;
}
int setC(char* szStr)
{
if (strlen(szStr) > 127) {
return 1;
}
strcpy(c, szStr);
return 0;
}
int getA() { return a; }
double getB() { return b; }
char* getC() { return c; }
};
int main()
{
CFoo obj;
Bar_t obj2;
cout << "初期化時のクラス:\n" <<
"\tobj.getA() = " << obj.getA() <<
"\n\tobj.getB() = " << obj.getB() <<
"\n\tobj.getC() = " << obj.getC() << endl;
cout << "初期化時の構造体:\n" <<
"\tobj2.getA() = " << obj2.getA() <<
"\n\tobj2.getB() = " << obj2.getB() <<
"\n\tobj2.getC() = " << obj2.getC() << endl;
obj.setA(10000);
obj.setB(11.11);
obj.setC("我が輩の年齢は10万と26歳である、フハハハハ!");
obj2.setA(-1);
obj2.setB(42.195);
obj2.setC("オラ、ワクワクしてきたぞ!");
cout << "代入時のクラス:\n" <<
"\tobj.getA() = " << obj.getA() <<
"\n\tobj.getB() = " << obj.getB() <<
"\n\tobj.getC() = " << obj.getC() << endl;
cout << "代入時の構造体:\n" <<
"\tobj2.getA() = " << obj2.getA() <<
"\n\tobj2.getB() = " << obj2.getB() <<
"\n\tobj2.getC() = " << obj2.getC() << endl;
return 0;
}

何となく命名規則をそれっぽくしてみたけど、こんな感じ?
ほとんどというか、private:とpublic:を付けてしまえば両方とも一緒という結果。

そんだけ。

Post to Twitter

, , ,

No Comments

C++でのメンバ変数として、動的オブジェクトを作る

C++を勉強していて何が困ったって、メインで動的にメモリ領域をとる方法はたくさん載っているのに、クラスのメンバ変数を動的にとる方法がなかなか載っていない。
と言うわけで、いろいろ試行錯誤した結果、下記のようになった。

テスト用コード

#include <iostream>
#include <string.h>
using namespace std;
class hoge
{
public:
char* szStr;
hoge(char* szNewStr)
{
szStr = new char[strlen(szNewStr)];
strcpy(szStr, szNewStr);
}
~hoge() { delete szNewStr; }
};
int main()
{
char szStr[] = {"ばかやろ~~~~~~~~~~~~~~~"};
cout << szStr << endl;
cout << "end" << endl;
cout << strlen(szStr) << endl;
hoge* obj;
obj = new hoge(szStr);
cout << "\ntest\n";
cout << szStr << endl;
delete obj;
return 0;
}

実行結果。

ばかやろ~~~~~~~~~~~~~~~
end
57
test
ばかやろ~~~~~~~~~~~~~~~

まず、hogeなるクラスを定義します、と。
そのメンバ変数に動的に確保したい型のポインタを置きます、と。
それをコンストラクタなり、なんなりでnewしてあげるとそこにで動的に確保される、と。
ちなみに、ここで言う「型」というのは文字通りの型じゃなくても良くて、クラスでも良い。
なぜかというと、C++でのクラスは構造体を拡張したものだから。

その詳細はまた別の記事にて。

そんだけ。

Post to Twitter

, , ,

No Comments

C++でbad_alloc例外を2回取るテスト

と言うか、2回エラーをキャッチすることができるンかい?と言うお話し。
ほら、メモリを取得するときに多めにとってダメで、少なめに取ったらOKかもしれない場合、どうなのかなぁ?とかそんなこと思っただけ。
まぁ、メモリ取れなかった時点で普通はもうやめちゃう訳なんだけれども。
一応出来るのか出来ないのかをはっきりさせたかった。

で、テストコード。

#include <iostream>
#include <iomanip>
#include <new>
using namespace std;
#define MEMSIZE 10000000
int main()
{
double* pDat;
int i = MEMSIZE;
while (true) {
try {
pDat = new double[i];
}
catch (bad_alloc) {
pDat = NULL;
break;
}
cout << "#1 count " << setiosflags( ios::right )
<< setw(2) << i/MEMSIZE << "  |  "
<< setw(9) << i << "bytes | "
<< setw(6) << i/1000 << "Kbytes | "
<< setw(3) << i/1000000 << "Mbytes\n";
delete pDat;
i += MEMSIZE;
}
cout << "after bad_alloc\n";
i -= MEMSIZE;
cout << i/1000000 << "Mbytes can take " << i/sizeof(char) << " characters\n\n";
i = MEMSIZE;
while (true) {
try {
pDat = new double[i];
}
catch (bad_alloc) {
pDat = NULL;
break;
}
cout << "#2 count " << setiosflags( ios::right )
<< setw(2) << i/MEMSIZE << "  |  "
<< setw(9) << i << "bytes | "
<< setw(6) << i/1000 << "Kbytes | "
<< setw(3) << i/1000000 << "Mbytes\n";
delete pDat;
i += MEMSIZE;
}
cout << "after bad_alloc\n";
i -= MEMSIZE;
cout << i/1000000 << "Mbytes can take " << i/sizeof(char) << " characters\n\n";
return 0;
}

実行結果。

#1 count  1  |   10000000bytes |  10000Kbytes |  10Mbytes
#1 count  2  |   20000000bytes |  20000Kbytes |  20Mbytes
#1 count  3  |   30000000bytes |  30000Kbytes |  30Mbytes
#1 count  4  |   40000000bytes |  40000Kbytes |  40Mbytes
#1 count  5  |   50000000bytes |  50000Kbytes |  50Mbytes
#1 count  6  |   60000000bytes |  60000Kbytes |  60Mbytes
#1 count  7  |   70000000bytes |  70000Kbytes |  70Mbytes
#1 count  8  |   80000000bytes |  80000Kbytes |  80Mbytes
#1 count  9  |   90000000bytes |  90000Kbytes |  90Mbytes
#1 count 10  |  100000000bytes | 100000Kbytes | 100Mbytes
#1 count 11  |  110000000bytes | 110000Kbytes | 110Mbytes
#1 count 12  |  120000000bytes | 120000Kbytes | 120Mbytes
#1 count 13  |  130000000bytes | 130000Kbytes | 130Mbytes
#1 count 14  |  140000000bytes | 140000Kbytes | 140Mbytes
#1 count 15  |  150000000bytes | 150000Kbytes | 150Mbytes
after bad_alloc
#2 count  1  |   10000000bytes |  10000Kbytes |  10Mbytes
#2 count  2  |   20000000bytes |  20000Kbytes |  20Mbytes
#2 count  3  |   30000000bytes |  30000Kbytes |  30Mbytes
#2 count  4  |   40000000bytes |  40000Kbytes |  40Mbytes
#2 count  5  |   50000000bytes |  50000Kbytes |  50Mbytes
#2 count  6  |   60000000bytes |  60000Kbytes |  60Mbytes
#2 count  7  |   70000000bytes |  70000Kbytes |  70Mbytes
#2 count  8  |   80000000bytes |  80000Kbytes |  80Mbytes
#2 count  9  |   90000000bytes |  90000Kbytes |  90Mbytes
#2 count 10  |  100000000bytes | 100000Kbytes | 100Mbytes
#2 count 11  |  110000000bytes | 110000Kbytes | 110Mbytes
#2 count 12  |  120000000bytes | 120000Kbytes | 120Mbytes
#2 count 13  |  130000000bytes | 130000Kbytes | 130Mbytes
#2 count 14  |  140000000bytes | 140000Kbytes | 140Mbytes
#2 count 15  |  150000000bytes | 150000Kbytes | 150Mbytes
after bad_alloc

結論、出来るw

おそまつ。

Post to Twitter

, , ,

No Comments

インライン関数について考える

一個前の投稿ではCの仮引数付きマクロについて取り上げたけど、本題はC++のインライン関数だったり。
仮引数付きマクロでは()で正しく演算順序を指定しないと問題が起きがちですよ、と。
で、CからインクリメントしたC++ではそれを一歩上行くインライン関数と言う物があり、より関数に近い形で置換できると言う優れもの。
使い方は以下の通り。

#include <iostream>
using namespace std;
#define PI 3.141592674
inline double area_of_circle(double r)
{
return r * r * PI;
}
int main()
{
double radius;
cout << "円の半径を入力して下さい。\n";
cin >> radius;
cout << "円の面積は" << area_of_circle(radius - 1.0 + 1.0) << "です。\n";
return 0;
}

実行結果は以下の通り

McLaren% ./inline_test
円の半径を入力して下さい。
4
円の面積は50.2655です。

18行目のように、わざと計算を挟んでみた。
仮引数付きマクロならば

radius - 1.0 + (1.0 * radius) - 1.0 + (1.0 * PI)

となるところを、

(radius - 1.0 + 1.0) * (radius - 1.0 + 1.0) * PI

と言うように、パーレンで囲わなくてもコンパイラが適宜最適化してくれると言う仕組み。
型の指定をしていることからわかる通り、型もチェックしてくれます、と。
これは便利。

ただ、独習C++によると

inline指定子は、コンパイラにとってはコマンドではなく要求であることを覚えておいてください。

とのこと。
どうやらループとか書いてあるとダメなコンパイラもあるらしい。
ただ、普及しているコンパイラはOKっぽい?
そもそも自分で明示的にインライン関数にするのは好ましくないから、コンパイラが自動的にインライン化する機能を有しているので、それに任せた方が良いんだとか。
ん〜、さすが後発言語。
何から何まで便利。

で、インライン化できなかった場合は普通の関数になるらしい。
ここで疑問になるのがプロトタイプ宣言の是非なんだけど、Wikipediaによれば

インライン関数はモジュール単位に定義する必要がある(通常の関数は1つのモジュールで定義すればよい)。これにより、モジュール単位に独立したコンパイルができるようになっている。

となる。
と言うことは、モジュール単位で書くのが当然で、外部から呼び出すのは無理?もしくはナンセンス?
一応ちょっと試したんだけど、ヘッダに書いたら読める。
ただ、別ソースファイルに書いたら読めなかった。
なるほど、独立しているとはそう言うことなのか??

一応動くソースは下記の通り。
(文字列を渡すところで警告あり。でも動く。)

inline_test_main.cpp

#include <iostream>
#include "inline.h"
using namespace std;
int main()
{
double radius;
echo("円の半径を入力して下さい。");
cin >> radius;
cout << "円の面積は" << area_of_circle(radius) << "です。\n";
return 0;
}

inline.h

#include <iostream>
using namespace std;
#define PI 3.141592674
void echo(char []);
inline double area_of_circle(double r)
{
return r * r * PI;
}

inline.cpp

#include "inline.h"
void echo(char str[])
{
cout << str << endl;
}

コンパイルと実行結果は下記のとおり。

McLaren% g++ -o inline inline_test_main.cpp inline.cpp
inline_test_main.cpp: In function ‘int main()’:
inline_test_main.cpp:9: 警告: deprecated conversion from string constant to ‘char*’
McLaren% ./inline
円の半径を入力して下さい。
3
円の面積は28.2743です。

そんだけ。

Post to Twitter

, , , ,

No Comments