BattleField 3 キター、てことでようやく家にも届きました。
しかし、届いた後に Windows Vista 以降にしか対応してないことを知る…。うちは既存ゲームをキープしておきたいのでいまだ XP だったわけで、あわてて Windows7 Pro と増設ドライブを注文。翌日の夜になってようやくこれらが届き、ドライブ増設~デュアルブートとして Windows7 をインストール、ドライバ類のインストール…と長きを経てようやく BattleField3 をインストール…、と思ったら Origin とかいう Steam もどきが必須のようで、DVD のインストーラを起動するとこの Origin をインストールする画面へ。ここからごたごた。
Origin の ID を登録しなければならないのですが、まず1回目の登録で未入力項目があったためエラーが発生。これはこっちの失敗なので仕方ないのですが、再度登録しようとすると、メールアドレスが登録済みというエラーが。どうもエラーではじかれてもメールアドレスのみ登録済み扱いになってしまっているようで、もうね(ry。
仕方なく別のメールアドレスで登録。どうやら登録は完了したらしいが、今度はログインではじかれる。パスワードが違う??仕方なくパスワードのリセットをし、ログイン成功。しかし、その後再起動してログインしようとするとまたはじかれる。…うーん、どうもパスワードが長すぎるとパスワードリセット画面からはログインできるが、通常ログイン画面からはログインできないっぽい…。もうね、もうね(ry。ちなみに失敗したパスワードは 20 文字。パスワードを短めにしたらとりあえず大丈夫っぽいので、おそらく原因はこれ。
その後、デュアルブートの XP 側から Origin をアンインストールしたのですが、ここで XP 側のドライブ名が変更されてしまっていて、なんと Windows7 側の Origin が削除されてしまった orz もう、どんだけトラブル起こりまくるんだ…いや、これはこっちの環境のせいなんだけれども。仕方なく Windows7 側で再インストールしようと BattleField3 の DVD から AutoRun を起動したのですが、インストールボタンを押した後無反応でソフトが終了。インストール済み扱いになってインストールしてくれないらしい…。とりあえず、レジストリエディタで Origin 関係らしきものを全部消してみるもまったく反応なし。
結局のところ、Origin インストール後に Origin が自動アップデートしたため、DVD 上の Origin はバージョンが古く上書きインストールにすらいかず終了する、といった流れらしい。もうね、どうしろと。で、Steam みたく、単独でクライアント配布してるんじゃないかと Origin ウェブサイトを探してみたがいっこうにみつからず、最後の希望を掛けて Google 先生で「ea origin download」でぐぐってみたところ、ありました!
http://www.origin.com/about
ここから Origin の最新版をダウンロードして実行したところ、「同じバージョンがすでにありますが、上書きインストールしますか?」というようなメッセージが表示され、「はい」でようやくインストールしてくれました!
長かった…。こちらの環境が特殊なせいも多少はあるものの、Origin の仕様がひどすぎではないですか、これ?パスワードのバグ、バージョンが古いと再インストールできない、クライアントの単独ダウンロードが Origin ウェブサイトのトップページからたどってもいっこうに見つからない、…と、ほんと手間かかりすぎ orz
疲れたので BattleField3 のレビューはまた今度~
2011年11月8日火曜日
2011年9月30日金曜日
Java って…
愚痴です。
最近仕事で Android の開発をしているのですが、Java の扱いづらさに悪戦苦闘しております。
もともと Java の利点は、プラットフォームに関係なく JavaVM 上で共通のプログラムが動く、というものだったと思います。最初は 100% Pure Java なんて言い方をしてましたね、確か。それが現在は、jni などという仕組みを導入してネイティブコードの呼び出しを許してしまうことで、この利点は失われてしまいました。さらに、携帯や Android は独自のライブラリを持っており、各機種で共通に動作するようなプログラムを作ることもできません。
さらには Java という言語の仕様も C++ プログラマから見るとあまりにも不完全で、ガベッジコレクションのような機能も、もはや不便なものでしかありません (後述)。
Java ってもう、いろいろ駄目なんじゃね?という愚痴です。
●ガベッジコレクションという足枷
ガベッジコレクションは、不要になった (どこからも参照されていない) メモリを自動的に解放してくれるものですが、これが C++ に慣れたプログラマにとっては非常に不便な機能です。なぜならこれは、いつ解放されるかプログラマが知ることはできないからです。
C++ プログラマは普通、初期化処理をコンストラクタに書き、終了処理をデストラクタに書きます。そして、そのクラスをスコープの中で使用すると、処理がスコープの外に出る際に必ずデストラクタが呼ばれることがわかっています。ですから、メモリの解放以外の終了処理、例えばリソースの解放処理やファイルのクローズをデストラクタに書き、これらの終了処理のし忘れを確実に回避することができます。
しかし Java や .NET のようなガベッジコレクションのシステムではメモリがいつ解放されるかわからない=デストラクタの呼ばれるタイミングがわからないため、このような終了処理をデストラクタに書くことができません。つまり、ファイルのクローズなどをプログラマが毎回手動で行うしかないわけです。これにより、終了処理のし忘れというバグをいつまでも作り続ける恐れがあります。
メモリの自動開放についても、C++ ではスマートポインタの導入により、昔のようなメモリリーク問題はほぼなくなってきています。しかも、スマートポインタならメモリ解放のタイミングもプログラマが予測可能なので、メモリ残量のコントロールも可能です。
ガベッジコレクションはこのように、予測不能であるために、プログラマにとっては足枷でしかないのです。
●処理が異常に重い
どこかの段階から Java では JIT という機能が追加され、内部で Java マシンコードを各プラットフォームにネイティブなコードに置き換え高速化するという機能が実装されたそうです。しかし、JIT で最適化する際の効率化のため、Java コンパイラ自体はほとんど最適化を行わないのだとか。そのおかげで JIT を搭載していない携帯機等では、とてつもなく動作速度が遅くなります (Android は 2.2 以降で実装されているそうですが)。これをなんとかするために、携帯機のプログラマは涙ぐましいまでの苦労を強いられます。
特にひどいのが、オブジェクト指向なプログラムをすると呼び出しコストが高くなるので、クラスの数をなるべく少なくする必要があるというものです。僕が見たものの中には、クラスが1つしかなく、その中に大量のメンバ変数と、メソッドが並んでいるというようなものもありました。昔懐かしい構造化プログラミングとかが必要になるレベルです。
JIT を搭載していないシステムでは、まともなものは作れなくなっているように思います。
●プリプロセッサがない
移植性を高くするため、機種依存のコードは、コンパイル時に選択するようにしたいものです。C 言語では昔からプリプロセッサを利用して、このようなコードの切り分けを行ってきました。
しかし、Java にはこのようなものがありません。多くの Java プログラマは C 言語用のプリプロセッサを利用しているようです。
このような状況であるにもかかわらず Java がプリプロセッサを正式に採用しないのはなぜなんでしょう?一部には Java のコンセプトに合わないとかなんとか。しかし、これがないと、機種毎に違うコードを用意するためにソースを別々に書き、別々にメンテナンスする必要が発生します。これはプログラマにとって大きな負荷となり、更新し忘れなどが頻繁に発生することになるでしょう。
バグを増やす可能性を増やしてまでこだわるコンセプトってなんですか?と問いたい。
●開発環境
Java での統合開発環境は eclipse が一般的のようです。eclipse は Java で書かれているそうで、このため処理が異常に重いです。また、メモリも大量に必要とし、メモリが不足すると落ちます。あまりに不便なため、コマンドラインだけで開発している人も多いのではないでしょうか?しかし、デバッガは GUI が欲しいところ…。
こだわりなのかなんなのか知りませんが、なんでもかんでも Java である必要はないのではないでしょうか。
…とかいろいろ書きましたが現状どうしようもなく、仕事はこれでやらなきゃならないわけで。
まあ、Anidroid 自体の開発はおもしろそうなので、個人的に自分専用の Andoird 開発用 IDE でも作ってみるかな。もちろん C++ で!
最近仕事で Android の開発をしているのですが、Java の扱いづらさに悪戦苦闘しております。
もともと Java の利点は、プラットフォームに関係なく JavaVM 上で共通のプログラムが動く、というものだったと思います。最初は 100% Pure Java なんて言い方をしてましたね、確か。それが現在は、jni などという仕組みを導入してネイティブコードの呼び出しを許してしまうことで、この利点は失われてしまいました。さらに、携帯や Android は独自のライブラリを持っており、各機種で共通に動作するようなプログラムを作ることもできません。
さらには Java という言語の仕様も C++ プログラマから見るとあまりにも不完全で、ガベッジコレクションのような機能も、もはや不便なものでしかありません (後述)。
Java ってもう、いろいろ駄目なんじゃね?という愚痴です。
●ガベッジコレクションという足枷
ガベッジコレクションは、不要になった (どこからも参照されていない) メモリを自動的に解放してくれるものですが、これが C++ に慣れたプログラマにとっては非常に不便な機能です。なぜならこれは、いつ解放されるかプログラマが知ることはできないからです。
C++ プログラマは普通、初期化処理をコンストラクタに書き、終了処理をデストラクタに書きます。そして、そのクラスをスコープの中で使用すると、処理がスコープの外に出る際に必ずデストラクタが呼ばれることがわかっています。ですから、メモリの解放以外の終了処理、例えばリソースの解放処理やファイルのクローズをデストラクタに書き、これらの終了処理のし忘れを確実に回避することができます。
しかし Java や .NET のようなガベッジコレクションのシステムではメモリがいつ解放されるかわからない=デストラクタの呼ばれるタイミングがわからないため、このような終了処理をデストラクタに書くことができません。つまり、ファイルのクローズなどをプログラマが毎回手動で行うしかないわけです。これにより、終了処理のし忘れというバグをいつまでも作り続ける恐れがあります。
メモリの自動開放についても、C++ ではスマートポインタの導入により、昔のようなメモリリーク問題はほぼなくなってきています。しかも、スマートポインタならメモリ解放のタイミングもプログラマが予測可能なので、メモリ残量のコントロールも可能です。
ガベッジコレクションはこのように、予測不能であるために、プログラマにとっては足枷でしかないのです。
●処理が異常に重い
どこかの段階から Java では JIT という機能が追加され、内部で Java マシンコードを各プラットフォームにネイティブなコードに置き換え高速化するという機能が実装されたそうです。しかし、JIT で最適化する際の効率化のため、Java コンパイラ自体はほとんど最適化を行わないのだとか。そのおかげで JIT を搭載していない携帯機等では、とてつもなく動作速度が遅くなります (Android は 2.2 以降で実装されているそうですが)。これをなんとかするために、携帯機のプログラマは涙ぐましいまでの苦労を強いられます。
特にひどいのが、オブジェクト指向なプログラムをすると呼び出しコストが高くなるので、クラスの数をなるべく少なくする必要があるというものです。僕が見たものの中には、クラスが1つしかなく、その中に大量のメンバ変数と、メソッドが並んでいるというようなものもありました。昔懐かしい構造化プログラミングとかが必要になるレベルです。
JIT を搭載していないシステムでは、まともなものは作れなくなっているように思います。
●プリプロセッサがない
移植性を高くするため、機種依存のコードは、コンパイル時に選択するようにしたいものです。C 言語では昔からプリプロセッサを利用して、このようなコードの切り分けを行ってきました。
しかし、Java にはこのようなものがありません。多くの Java プログラマは C 言語用のプリプロセッサを利用しているようです。
このような状況であるにもかかわらず Java がプリプロセッサを正式に採用しないのはなぜなんでしょう?一部には Java のコンセプトに合わないとかなんとか。しかし、これがないと、機種毎に違うコードを用意するためにソースを別々に書き、別々にメンテナンスする必要が発生します。これはプログラマにとって大きな負荷となり、更新し忘れなどが頻繁に発生することになるでしょう。
バグを増やす可能性を増やしてまでこだわるコンセプトってなんですか?と問いたい。
●開発環境
Java での統合開発環境は eclipse が一般的のようです。eclipse は Java で書かれているそうで、このため処理が異常に重いです。また、メモリも大量に必要とし、メモリが不足すると落ちます。あまりに不便なため、コマンドラインだけで開発している人も多いのではないでしょうか?しかし、デバッガは GUI が欲しいところ…。
こだわりなのかなんなのか知りませんが、なんでもかんでも Java である必要はないのではないでしょうか。
…とかいろいろ書きましたが現状どうしようもなく、仕事はこれでやらなきゃならないわけで。
まあ、Anidroid 自体の開発はおもしろそうなので、個人的に自分専用の Andoird 開発用 IDE でも作ってみるかな。もちろん C++ で!
2011年6月17日金曜日
終・スマートポインタの別の解
さて、前回は weak_ptr は使わずに新しいクラス distributable_ptr を作成しました。実現したいことは、weak_ptr に近いんですが shared_ptr を必要としないというところがミソなわけです。
で、その構造を見てもらうとわかるんですが、参照数をカウントしておいて、なくなったら削除するという仕組みはスマートポインタとまったく一緒です。じゃあ、distributed_ptr は shared_ptr としても機能するのでは?とも考えられます。
で、distributed_ptr に shared_ptr として機能させるためのコンストラクタを追加し、デストラクタにも処理を追加します。
・shared_ptr + weak_ptr よりも構築が簡単で安全。
・スタックで確保しても、配列で確保しても、new で確保しても構わない。
・スマートポインタで管理するかどうかは任意に選ぶことができる。
・weak_ptr のように自身のポインタを distributed_ptr として配布する事ができる。
といった所でしょうか。ただ、自身のポインタを外部に配布する必要がない場合には不要となる機能が多いので、shared_ptr をすべてこれに置き換えればいいというものでもありません。
shared_ptr の代わりとして使うには機能がまだまだ足りませんが、いろいろ機能付け足してテストもして後日公開したいと思います。
で、その構造を見てもらうとわかるんですが、参照数をカウントしておいて、なくなったら削除するという仕組みはスマートポインタとまったく一緒です。じゃあ、distributed_ptr は shared_ptr としても機能するのでは?とも考えられます。
で、distributed_ptr に shared_ptr として機能させるためのコンストラクタを追加し、デストラクタにも処理を追加します。
template <class T> distributed_ptr<T>::distributed_ptr( distributable_ptr<T>* org ) : data( org->data ) { // このコンストラクタを使用した場合はスマートポインタモードになります。 // data が持っている distributable_ptr が最初に +1 した参照数は、この // ポインタが参照したことにし、distributable_ptr 自体は weak_ptr のよう // に参照数を +1 していないものと考えます。 } template <class T> distributed_ptr<T>::~distributed_ptr( void ) { // スマートポインタモードの時、最後の distributed_ptr が破棄されると、 // 参照数が 0 になります。 // スマートポインタモードでなければ、distributable_ptr が参照数 1 を // 保持していますので、すべての distributed_ptr が破棄されても参照数は // 0 にはなりません。 T* ptr = this->data->get(); if( !distributable_data<T>::dereference( this->data ) ) delete ptr; }この追加によって、distributed_ptr は shared_ptr としても機能することになります。つまり、
class Hoge : public distributable_ptr<Hoge> { public : Hoge( void ) {} virtual ~Hoge( void ) {} }; void foo() { distributed_ptr<Hoge> ptr( new Hoge ); ... 処理 ... }のように shared_ptr っぽい書き方をするとスマートポインタとして機能し、関数 foo() を抜ける際にはちゃんと Hoge のインスタンスが破棄されるようになります。当然、前回書いたような機能もそのまま使えます。この distributable_ptr 及び distributed_ptr の特徴としては
・shared_ptr + weak_ptr よりも構築が簡単で安全。
・スタックで確保しても、配列で確保しても、new で確保しても構わない。
・スマートポインタで管理するかどうかは任意に選ぶことができる。
・weak_ptr のように自身のポインタを distributed_ptr として配布する事ができる。
といった所でしょうか。ただ、自身のポインタを外部に配布する必要がない場合には不要となる機能が多いので、shared_ptr をすべてこれに置き換えればいいというものでもありません。
shared_ptr の代わりとして使うには機能がまだまだ足りませんが、いろいろ機能付け足してテストもして後日公開したいと思います。
2011年6月16日木曜日
続々・スマートポインタの問題…というよりも
前回の続きになります。
そもそも、スマートポインタの目的とは、new したものを delete し忘れないようにするためのものでした。今実現したいのは、クラス内から this を安全に外部へ渡したいということで、スマートポインタとは目的が違うような気がします。
つまり、shared_ptr を必要としない weak_ptr があればいいのでは?ということで
※まだ動作検証してません。
で、使うときは、こんな感じに継承して使います。わざわざ継承させるのは、クラスが破棄されたときにコピーされたポインタを無効化するためです。
distributable_data を構築するのは最初に lock() 関数が呼ばれたときにした方がとか、distributed_ptr のコピー処理とか、スレッドセーフとかも考えるともっといろいろ工夫が必要ですが、とりあえずこんな感じで。しかし、そして、さらに野望が...続く。
そもそも、スマートポインタの目的とは、new したものを delete し忘れないようにするためのものでした。今実現したいのは、クラス内から this を安全に外部へ渡したいということで、スマートポインタとは目的が違うような気がします。
つまり、shared_ptr を必要としない weak_ptr があればいいのでは?ということで
// ポインタ情報 template <class T> class distributable_data { public : distributable_data( T* ptr_ ) : ptr( ptr_ ), count( 1 ) { } long use_count( void ) const { return this->count; } void reference( void ) { this->count++; } T* get( void ) { return this->ptr; } const T* get( void ) const { return this->ptr; } void clear( void ) { this->ptr = 0; } static bool dereference( distributable_data<T>* data ) { data->count--; if( data->use_count() == 0 ) { delete data; return false; } else { return true; } } private : T* ptr; long count; // ポインタ先ではなく、このクラスの参照数 }; // 配布されるポインタ template <class T> class distributed_ptr { public : distributed_ptr( distributable_data<T>* data_ ) : data( data_ ) { this->data->reference(); } distributed_ptr( const distributed_ptr<T>& org ) : data( org.data ) { this->data->reference(); } ~distributed_ptr( void ) { distributable_data<T>::dereference( this->data ); } bool expire( void ) const { return this->data->get() == 0; } T* operator->( void ) { return this->data->get(); } const T* operator->( void ) const { return this->data->get(); } private : distributable_data<T>* data; }; // 配布可能ポインタ template <class T> class distributable_ptr { public : distributable_ptr( void ) { this->data = new distributable_data<T>( dynamic_cast<T*>(this) ); } virtual ~distributable_ptr( void ) { if( distributable_data<T>::dereference( this->data ) ) { this->data->clear(); } } distributed_ptr<T> lock( void ) { return distributed_ptr<T>( this->data ); } private : distributable_data<T>* data; };こんな感じかな?
※まだ動作検証してません。
で、使うときは、こんな感じに継承して使います。わざわざ継承させるのは、クラスが破棄されたときにコピーされたポインタを無効化するためです。
class Hoge : public distributable_ptr<Hoge> { public : Hoge( void ) : distributable_ptr<Hoge>() { } virtual ~Hoge( void ) { } void Func( void ) { } }; void Foo( void ) { Hoge hoge[ 10 ]; // ポインタ取得 distributed_ptr<Hoge> ptr = hoge[2].lock(); // 使用可能か確認してからアクセス if( !ptr.expire() ) ptr->Func(); }とりあえず、スタック上に配列で確保してみましたが、もちろん new で構築しても問題ありませんし、そのポインタを shared_ptr で管理していても全く問題ないはずです。
distributable_data を構築するのは最初に lock() 関数が呼ばれたときにした方がとか、distributed_ptr のコピー処理とか、スレッドセーフとかも考えるともっといろいろ工夫が必要ですが、とりあえずこんな感じで。しかし、そして、さらに野望が...続く。
2011年6月15日水曜日
続・スマートポインタの問題(?
前回の続き。
と、その前に前回少し触れた weak_ptr について軽く説明しておきます。
weak_ptr の元々の用途としては、shared_ptr が指し示すクラスが自身を差すポインタを持つ次のような場合、
これを解決するために、自身を差すようなポインタを持つ場合は weak_ptr というクラスを使用しましょうということになりました。weak_ptr は参照数を+1しないコピーであり、ここから新しいコピーを作成することもできます。
ただし weak_ptr は参照数を+1しないため、参照数が0になった時点でポインタが破棄され、コピーが作成できなくなります。ですので、weak_ptr が外部にあるときには、正常に取得できたかどうか確認する必要があります。
さて、この weak_ptr を使えば前回言ったような「外部に渡すためのポインタ」が確保できるようになりますが、これは非常に面倒な作りです。上の例の Hoge は「必ず new で確保しなければならない」「shared_ptr で管理しなければならない」「構築した後、self_ptr を作成しなければならない」という制限がついて回ることになります。これらをまとめて行う関数を作ることはできますが、もし Hoge の派生クラスを作ると、その派生クラス用の構築関数も作る必要が出てきます。また、このクラスの配列を構築したい場合、スタックに構築したい場合にも対応できません。
これらのことから、weak_ptr は良い解決方法とは言えない、と思われるわけです。
…何か長くなってきたので続く。
と、その前に前回少し触れた weak_ptr について軽く説明しておきます。
weak_ptr の元々の用途としては、shared_ptr が指し示すクラスが自身を差すポインタを持つ次のような場合、
class Hoge { public : shared_ptr<Hoge> self_ptr; }; void Foo() { shared_ptr<Hoge> ptr( new Hoge ); // ① ptr->self_ptr = ptr; // ② } // ③関数 Foo() を抜けても、構築された Hoge は解放されません。①の時点でポインタの参照数は1です。②でコピーされ、参照数は2になります。そして、③の時点で ptr は解放され、参照数は1になります。つまり、ptr はスコープを抜けて破棄されますが、コピーされた self_ptr は破棄される機会がないのです。
これを解決するために、自身を差すようなポインタを持つ場合は weak_ptr というクラスを使用しましょうということになりました。weak_ptr は参照数を+1しないコピーであり、ここから新しいコピーを作成することもできます。
class Hoge { public : weak_ptr<Hoge> self_ptr; }; void Foo() { shared_ptr<Hoge> ptr( new Hoge ); // ① ptr->self_ptr = ptr; // ② shared_ptr<Hoge> new_ptr = ptr->self_ptr.lock(); // ③ } // ④①の時点で参照数は1です。②では、weak_ptr を作っているだけなので、参照数は変わらず1のままです。③では weak_ptr のメンバ関数 lock() を呼ぶことでこのスマートポインタのコピーを作成しているので参照数が2になります。そして、④では ptr 及び new_ptr がスコープから外れるので参照数は-2され0になります。このため、ここで Hogeのポインタは破棄されます。
ただし weak_ptr は参照数を+1しないため、参照数が0になった時点でポインタが破棄され、コピーが作成できなくなります。ですので、weak_ptr が外部にあるときには、正常に取得できたかどうか確認する必要があります。
void Foo() { shared_ptr<Hoge> ptr( new Hoge ); // ① weak_ptr<Hoge> wptr( ptr ); // ② ptr.reset(); // ③ shared_ptr<Hoge> new_ptr = wptr.lock(); // ④ }①②は前の例とおなじで、参照数はここまでで1です。そして、③の時点で ptr が参照数を-1するので参照数は0になり Hoge は破棄されます。次の④でコピーを作成しようとしますが、ポインタはすでに破棄されていますので、返されるポインタは null になります。ですので、このような使い方をする場合には、取得されたポインタが有効かどうかを確認してから使う必要があります。
さて、この weak_ptr を使えば前回言ったような「外部に渡すためのポインタ」が確保できるようになりますが、これは非常に面倒な作りです。上の例の Hoge は「必ず new で確保しなければならない」「shared_ptr で管理しなければならない」「構築した後、self_ptr を作成しなければならない」という制限がついて回ることになります。これらをまとめて行う関数を作ることはできますが、もし Hoge の派生クラスを作ると、その派生クラス用の構築関数も作る必要が出てきます。また、このクラスの配列を構築したい場合、スタックに構築したい場合にも対応できません。
これらのことから、weak_ptr は良い解決方法とは言えない、と思われるわけです。
…何か長くなってきたので続く。
2011年6月14日火曜日
スマートポインタの問題点
C++/CLI という仕様ではガベッジコレクションにより、ポインタ解放の義務がありません。これは、使ったことのある人ならわかると思いますが非常に便利な機能で、必要に応じていくらでもインスタンスを構築し、不要になったらそのまま放置しとけば勝手に裏で解放してくれます。まあ、これはこれで別の問題があるのですが。
最初は、ポインタをすべてこのような形で使用するよう心がければ、ガベッジコレクションなどなくても C++/CLI と同様にポインタ管理しなくてもよくなる、と考えていました。作られたインスタンスにアクセスするためには、元のスマートポインタまたはそのコピーを使用する必要があるわけですから、不意に解放されたポインタにアクセスしてしまう心配もありません。
しかし、やはりというか、問題がありました。このスマートポインタまたはそのコピーを使用せずにインスタンスにアクセスするケースがあります。それは、そのクラス自身のメンバ関数です。メンバ関数は this というポインタを使用することによってスマートポインタを経由せずに、いつでも自由にそのインスタンスにアクセスすることができてしまいます。メンバ関数が実行されているということは、そのインスタンスが存在するということですから一見問題ないように思いますが、その this を外部の関数等に渡すことを考えるとどうでしょう?その関数でポインタを保存しておいて、別の機会にアクセスされるとしたら、そのポインタが有効かどうかが別の方法によって知らされない限り不正アクセスが発生する危険性があります。
クラス Hoge に weak_ptr をメンバとして持たせておいて、クラス Hoge を構築する度にそのスマートポインタから weak_ptr を作成しセットする、というのも考えましたがどうにも面倒です。そもそも、クラスを構築するために必ず new で確保しスマートポインタに入れなければならないということを義務づけるのも、あまり好ましくありません。
これらのことから、このスマートポインタだけではガベッジコレクションの代わりはできそうにないな、と結論しました。そして、別の手段を模索することになります。...続く。
標準 C++ にも似たようなことを実現するライブラリがあります。スマートポインタと呼ばれるもので、次期仕様である C++0x に取り込まれる予定で、現在でも boost ライブラリを導入することによってすぐにでも使うことのできる機能です。
void foo() { boost::shared_ptr<Hoge> ptr0( new Hoge ); // ポインタと同じように使うことができる ptr->Func(); // コピーすると参照カウンタが +1 される boost::shared_ptr<Hoge> ptr1 = ptr0; // ptr0, ptr1 の両方のインスタンスがなくなると、 // 自動的に delete される }
最初は、ポインタをすべてこのような形で使用するよう心がければ、ガベッジコレクションなどなくても C++/CLI と同様にポインタ管理しなくてもよくなる、と考えていました。作られたインスタンスにアクセスするためには、元のスマートポインタまたはそのコピーを使用する必要があるわけですから、不意に解放されたポインタにアクセスしてしまう心配もありません。
しかし、やはりというか、問題がありました。このスマートポインタまたはそのコピーを使用せずにインスタンスにアクセスするケースがあります。それは、そのクラス自身のメンバ関数です。メンバ関数は this というポインタを使用することによってスマートポインタを経由せずに、いつでも自由にそのインスタンスにアクセスすることができてしまいます。メンバ関数が実行されているということは、そのインスタンスが存在するということですから一見問題ないように思いますが、その this を外部の関数等に渡すことを考えるとどうでしょう?その関数でポインタを保存しておいて、別の機会にアクセスされるとしたら、そのポインタが有効かどうかが別の方法によって知らされない限り不正アクセスが発生する危険性があります。
クラス Hoge に weak_ptr をメンバとして持たせておいて、クラス Hoge を構築する度にそのスマートポインタから weak_ptr を作成しセットする、というのも考えましたがどうにも面倒です。そもそも、クラスを構築するために必ず new で確保しスマートポインタに入れなければならないということを義務づけるのも、あまり好ましくありません。
これらのことから、このスマートポインタだけではガベッジコレクションの代わりはできそうにないな、と結論しました。そして、別の手段を模索することになります。...続く。
2011年6月11日土曜日
Radeon HD6750
Left4Dead2 を最近ごりごり遊んでるわけなんですが、ロードがいちいち遅く他の人に出遅れがち、ゲーム開始時には必ず数秒間ラグる、といった不満がありました。まあ、GeForce9600GT とかもうかなり古い世代だよね、新しいのに買い換えよう、ということで買ってきました HD6750。
で、入れ替えたら当然ベンチしたくなるというもの。ためしにモンハンベンチマークをやってみました。
うちのスペック:
CPU: PhenomII X4 965 (3.41GHz)
Mem: 4GB (XP 32bit のため、実質 3.25GB)
M/B: M4A77TD Pro
VGA Driver: Catalyst 11.5
モンハンベンチ結果:
1280x768
1st SCORE:8988
絆 SCORE:6662
1st SCORE:8988
絆 SCORE:6662
まあ、こんなものなのかなと、他の人の結果とかも検索してみる。
orz
なにさ、30000 とか 60000 とか。桁が違いすぎる。何よりショックだったのは、これまで使ってきた 9600GT でも SCORE 8000台出るそうで。買い換えあんま意味なかったのか、ううっ(泣
まあ、メモリも 2GB→3.25GB に増設したおかげもあって、確実にロード時間やらラグやらも減ったので効果はゼロではなかったのでよしとしよう。家の事情で、ファンレスのカードしか選択肢がなかったというのもありますし。
2011年5月28日土曜日
最近は Left 4 Dead 2
Left 4 Dead 2 が熱い。発売はちょっと前ですが、友人に聞いて買ってみたところなかなかに楽しい。
設定は、どこかの町や村がゾンビだらけになり、感染せずに済んだ4人が銃やら斧やらを手に脱出を試みる、てな感じのよくあるゾンビゲーなんですが、基本的にマルチプレイなので協力して進んでいく必要があります。武器は、ショットガンやライフル等いろいろありますが、CounterStrike なんかやっていた人ならお馴染みの M4A1 や AK、DE なんかもあるため、経験者は馴染みやすいでしょう。リコイルなんかも個人的にはほぼ同じだと思います。
襲ってくる感染者の中には特殊感染者という強力な奴が何種類かいて、こいつらに襲われると自力では脱出不可能になります。例えばチャージャーという奴は、いきなり猛スピードで突っ込んできて体当たりされ、その後首根っこ捕まれてガンガン地面に叩きつけられます。こうなると、自力ではどうすることもできなくなり、誰か他のプレイヤーがこのチャージャーを引き剥がすか、殺すかしてくれるのを待つだけになります。
また、HP が 0 になると、すぐには死なずに身動きできない状態になります。この時、他のプレイヤーが助け起こすことで、治療が必要な状態ながらも復帰することができます。ウィッチという特殊感染者に襲われると一撃でこの状態になりますので、他プレイヤーの助けが必須になります。
ゲームの雰囲気はバイオハザードのようなホラー系ではなく、ゾンビが大量にわらわらと走ってくるのをバカスカ撃ちまくって突破していくことになるアクション性が高い感じです。というかまあ、Half-Life 系のよくある FPS といった感じですが、Co-op ゲームなのがいいですね。昔、CounterStrike を改造して無理矢理 Co-op ゲームにしてサーバ立ててたことがあるんですが、なんかそんなのを思い出します。そういえば、SvenCoop も Source エンジンに移植するとか言っていたのはどうなったんだろ。まだやってるのかな…?
あ、それから最後に。ゾンビゲーなので当然ですが、グロ注意です。
設定は、どこかの町や村がゾンビだらけになり、感染せずに済んだ4人が銃やら斧やらを手に脱出を試みる、てな感じのよくあるゾンビゲーなんですが、基本的にマルチプレイなので協力して進んでいく必要があります。武器は、ショットガンやライフル等いろいろありますが、CounterStrike なんかやっていた人ならお馴染みの M4A1 や AK、DE なんかもあるため、経験者は馴染みやすいでしょう。リコイルなんかも個人的にはほぼ同じだと思います。
襲ってくる感染者の中には特殊感染者という強力な奴が何種類かいて、こいつらに襲われると自力では脱出不可能になります。例えばチャージャーという奴は、いきなり猛スピードで突っ込んできて体当たりされ、その後首根っこ捕まれてガンガン地面に叩きつけられます。こうなると、自力ではどうすることもできなくなり、誰か他のプレイヤーがこのチャージャーを引き剥がすか、殺すかしてくれるのを待つだけになります。
また、HP が 0 になると、すぐには死なずに身動きできない状態になります。この時、他のプレイヤーが助け起こすことで、治療が必要な状態ながらも復帰することができます。ウィッチという特殊感染者に襲われると一撃でこの状態になりますので、他プレイヤーの助けが必須になります。
ゲームの雰囲気はバイオハザードのようなホラー系ではなく、ゾンビが大量にわらわらと走ってくるのをバカスカ撃ちまくって突破していくことになるアクション性が高い感じです。というかまあ、Half-Life 系のよくある FPS といった感じですが、Co-op ゲームなのがいいですね。昔、CounterStrike を改造して無理矢理 Co-op ゲームにしてサーバ立ててたことがあるんですが、なんかそんなのを思い出します。そういえば、SvenCoop も Source エンジンに移植するとか言っていたのはどうなったんだろ。まだやってるのかな…?
あ、それから最後に。ゾンビゲーなので当然ですが、グロ注意です。
2011年4月24日日曜日
メモ:WinMain をスタティックライブラリに含む方法
「Unicode 文字セットを使用する」にした_tWinMain 関数を含んだスタティックライブラリを作成し、リンクしようとすると次のようなエラーが出てリンクに失敗します。
いろいろ調べた結果、ある掲示板で答えを見つけたのでメモ。exe を作る方のプロジェクトのプロパティで「リンカー」→「詳細設定」→「エントリポイント」の項目に wWinMainCRTStartup と書くだけ。
理由ははっきりとは不明ですが、もともと Win32 では「マルチ バイト文字セットを使用する」では WinMain() が呼ばれ、「Unicode 文字セットを使用する」では wWinMain() が呼ばれるわけですが、スタティックライブラリに含める場合はこの設定にかかわらず WinMain() が呼ばれてしまうようでエラーになってしまうみたいです。なんでこんな罠が…。
ちなみにこちらは VisualStudio 2010 でのお話です。
error LNK2019: 未解決の外部シンボル _WinMain@16 が関数 ___tmainCRTStartup で参照されました。 |
理由ははっきりとは不明ですが、もともと Win32 では「マルチ バイト文字セットを使用する」では WinMain() が呼ばれ、「Unicode 文字セットを使用する」では wWinMain() が呼ばれるわけですが、スタティックライブラリに含める場合はこの設定にかかわらず WinMain() が呼ばれてしまうようでエラーになってしまうみたいです。なんでこんな罠が…。
ちなみにこちらは VisualStudio 2010 でのお話です。
2011年4月17日日曜日
義援金とか
なんだか最近、募金しました、寄付しました、みたいな記事が出ると、「どうせ売名だろ」みたいなこと言うのをよく見かけます。…それってどうなんでしょう?
募金して、誰か傷つきますか?迷惑になる人がいますか?寄付することは悪い行為ですか?
僕が不安になるのは、そういう風潮でもって、募金するためにまるで悪いことでもするみたいにこそこそしなくちゃいけないみたいな、そういう世界になってしまうのはどうなのかなと。そんな雰囲気をつくってしまうと、本当に善意の人も尻込みしてしまうんじゃないかなと。
別に、そうやって自分したことを自慢したいだけなんだなー、って思っても「えらいね。」「一つ、いいことしたね。」って嫌味なしに素直に言ってあげればみんないい気分で終わるだろうし、そう言っていれば「ああ、みんな募金してるんだな、僕も/私もしておこうかな」って周りも同調しやすいだろうし。そうやって助け合うことが当たり前っていう風潮を広めるべきなんじゃないかなと。
だから、僕はそれが誰であろうと、例え本当に売名目的であったとしても関係なく、素直に「えらいっ。」「よくやった。」と言ってあげたいと思う。今日は何かそんなことを思った。
募金して、誰か傷つきますか?迷惑になる人がいますか?寄付することは悪い行為ですか?
僕が不安になるのは、そういう風潮でもって、募金するためにまるで悪いことでもするみたいにこそこそしなくちゃいけないみたいな、そういう世界になってしまうのはどうなのかなと。そんな雰囲気をつくってしまうと、本当に善意の人も尻込みしてしまうんじゃないかなと。
別に、そうやって自分したことを自慢したいだけなんだなー、って思っても「えらいね。」「一つ、いいことしたね。」って嫌味なしに素直に言ってあげればみんないい気分で終わるだろうし、そう言っていれば「ああ、みんな募金してるんだな、僕も/私もしておこうかな」って周りも同調しやすいだろうし。そうやって助け合うことが当たり前っていう風潮を広めるべきなんじゃないかなと。
だから、僕はそれが誰であろうと、例え本当に売名目的であったとしても関係なく、素直に「えらいっ。」「よくやった。」と言ってあげたいと思う。今日は何かそんなことを思った。
2011年3月17日木曜日
ちょっと気が楽になった
地震、そして原発と、関東在住ですがかなりびびってました。もう毎日毎夜ガクブルで、寝不足で…。
そしたら Twitter で、こんなのが回ってきました。
これ読んだら少し気が楽になりました。とりあえず、大丈夫そう。テレビとかだとひたすら不安感煽ることばっかりだし、ほんともうテレビとか二度と見ねえよっ。もともとほとんど見てないけど。
2011年3月5日土曜日
C++ クラスの private はどこまで private か?
先日、ふと気になった以下のようなコード:
必要があってできるかなと思ってやってみたらできたので、まあこれはこれで良かったのですが、これは C++ 標準の仕様なのか気になって調べてみました。そしたら海外の掲示板にて同じ疑問を持っていた仲間発見。結果を俺訳すると、
とのこと。ちゃんと標準仕様だったわけですね。最初から C++ の仕様書読めよ、ってツッコミされそうですが(汗
まとめると次のような感じ:
つまり、クラス A の private メンバにアクセスして欲しくないクラスはクラス A の外に書けってことですね。今までクラス外で使用する必要のないものはクラス内に書く、というスタンスだったんですが、これを考えるとちょっと考え方を変える必要がありそうです。
class A { private : int a; class B { public : B( A* p ) { p->a = 0; } }; };何が問題かというと、class A の private メンバである変数「a」に対し、class B から public メンバのようにアクセスしているわけです。で、これが普通にコンパイルを通ってしまう。
必要があってできるかなと思ってやってみたらできたので、まあこれはこれで良かったのですが、これは C++ 標準の仕様なのか気になって調べてみました。そしたら海外の掲示板にて同じ疑問を持っていた仲間発見。結果を俺訳すると、
C++ Standard 11.8 入れ子のクラス |
入れ子になったクラスは他のメンバと同じアクセス権を持ったメンバになります。このクラスを囲っているクラスは、入れ子になっているクラスに対して特別なアクセス権は持たず、通常のルールに従います。 |
まとめると次のような感じ:
class A { private : int a; class B { private : int b; public : B( A* p ) { p->a = 0; // OK } }; void func( B* p ) { p->b = 0; // エラー } };
つまり、クラス A の private メンバにアクセスして欲しくないクラスはクラス A の外に書けってことですね。今までクラス外で使用する必要のないものはクラス内に書く、というスタンスだったんですが、これを考えるとちょっと考え方を変える必要がありそうです。
2011年2月15日火曜日
「メソッドまたは操作は実装されていません。」だって。
ちょっと前に作ったソフトを修正するため VisualStudio 2010 を久々に起動しコンパイルをしようとすると、
「メソッドまたは操作は実装されていません。」
というダイアログがでてコンパイルできない症状が発生しました。
右クリックメニューからコンパイルを選択すると一応コンパイルはできるものの、実行しようとすると
「メソッドまたは操作は実装されていません。」
はて?
同ソリューション内のインストーラを作成するプロジェクトをビルドしようとしても
「メソッドまたは操作は実装されていません。」
…困った。
ググってみても同じような症状はほとんどなし。唯一似たような症状は「再現性なし」で解決されている始末。
ソリューションからプロジェクトを一つ消すと動作するのですが、再度プロジェクトを追加するとまた同じダイアログがでて動作しなくなる…。
結局、いろいろ調べた結果、VisualStudio の IDE にインストールしたプラグインが悪さしていると判明。うちの仕事関係の一般公開されてない代物なのですが、これをアンインストールしたら問題なく動作するようになりました。
問題が起こっていたのはそのプラグインに関連しないプロジェクトだったので、そこに行き当たるまでかなり時間をロスしました…orz
VisualStudio 自体のバグではなかったわけですが、何によって発生されたエラーかぐらい表示して欲しいものです…。
2011年1月25日火曜日
実はとても簡単な回転行列
3D のプログラミングをする上でかかせない回転行列。この中に入っている数値の意味を知らずに使っている人、結構いるんじゃないでしょうか。実は僕もわりと最近まで、その中に何が入っているかなんてあまり考えずに使っていました。
ググってみると、たいてい概念と計算式のみの説明だったり、DirectX 等に用意されている関数群を使ったサンプルのみだったりと、その中身について説明しているサイトは僕には見つけられませんでした。
理解している人にとっては当たり前のことなんだと思いますが、知らない人がその計算式だけで中身を理解するのはなかなか難しいと思います。
なので今回はこの行列の中身について解説したいと思います。
前提として、ベクトルの計算程度は理解している必要があります。逆に言えば、ベクトルの計算を理解していれば行列の中身も理解しているも同然と言えます。
まず、単位行列を見てみます。
この行列を、横に1行ずつ見て、それぞれ1行を1本のベクトルとして見てみます。3x3 行列ですから、3本のベクトルがあることになります。これを右手座標系の図で表すと、
こんな感じになります。上から1行目のベクトルが赤、2行目が緑、3行目が青になります。何か、よく見る図ですね。
さて次は Y 軸中心に 30 度回転させる行列を作ってみます。そこらでググれば、Y 軸中心に回転させる行列は、
だということなので、30 度回転させる行列は、
となると思います。ではこれも上と同じように1行ずつベクトルとして解釈して図にしてみます。
こんな感じになります。
…だいたいおわかりになるでしょうか?
つまり、3x3 の回転行列とは3本の垂直な単位ベクトルを並べただけのものなんです。本当にただそれだけのものなので、3D のライブラリなんかに用意されている行列計算関数なんか使わなくても簡単に行列は作れるんです。
例えば Z+ を向いているモデルを任意のベクトルの方向に向かせたければ、行列の3行目にそのベクトルを入れ、同ベクトルを XZ 平面上で 90 度回転させたベクトルを1行目に入れ、最後にこの2つのベクトルの外積を求めて2行目に入れればいいわけです。向かせたいベクトルが Y 軸方向に上下するならもうちょっと工夫が必要ですが、この理屈を理解していれば簡単に作れると思います。
2011年1月7日金曜日
続・確率の誤解 - 確率の数だけサイコロが
さて、前回書いた確率ですが、結構多くのプログラマが間違った使い方をしているので、また語ってみたいと思います。
ここに、1個のサイコロがあります。このサイコロは1~6までのそれぞれの数がでる頻度がまったく一緒で、6回ふれば必ず1~6までの数がそれぞれ1回ずつ出るものとします。
さて、このサイコロをAさんとBさんの二人が3回ずつ交互にふったとして、Aさん及びBさんが偶数の目を出す確率は何パーセントでしょうか?
答え:不定
確率50%ではありません。もし確率50%であるなら、Aさん及びBさんは必ず1~2回偶数の目を出すことになります。
しかし、ここではこれは保証されません。なぜなら、このサイコロが保証しているのは1~6までの数値が同じ頻度で出現するということだけであり、何回目に偶数が出るのかということについては何も保証していないからです。
ですから、Aさんが3回偶数を出してBさんが奇数を3回出してしまうことも、逆にBさんが3回偶数を出してしまうことも起こりうるわけです。
つまり、前回書いたような乱数発生器にも同じことが言えます。
1つの乱数発生器をアイテム出現率の他に、例えばアイテムの種類を選ぶための乱数としても利用していたとすると、アイテム出現の成功となるような値が発生する機会をアイテムの種類を選ぶ乱数を発生させる機会に奪われて、望まれる確率よりも低い出現率になってしまうこともあるわけです。
もしプログラマが乱数をこのような間違った使い方をしてしまった場合には、確率の数値はまるで当てになりません。そして、こういった理屈を理解していないプログラマが結構いるんですよね…。私も結構長い間、理解せずにやってましたしw
MHP は…どうなんでしょうね。ちゃんとわかってるプログラマが作ってるんでしょうか。前回書いたような要因もあるので、プレイしていても開発者の想定通りなのかどうかはまったくわかりませんw
ここに、1個のサイコロがあります。このサイコロは1~6までのそれぞれの数がでる頻度がまったく一緒で、6回ふれば必ず1~6までの数がそれぞれ1回ずつ出るものとします。
さて、このサイコロをAさんとBさんの二人が3回ずつ交互にふったとして、Aさん及びBさんが偶数の目を出す確率は何パーセントでしょうか?
答え:不定
確率50%ではありません。もし確率50%であるなら、Aさん及びBさんは必ず1~2回偶数の目を出すことになります。
しかし、ここではこれは保証されません。なぜなら、このサイコロが保証しているのは1~6までの数値が同じ頻度で出現するということだけであり、何回目に偶数が出るのかということについては何も保証していないからです。
ですから、Aさんが3回偶数を出してBさんが奇数を3回出してしまうことも、逆にBさんが3回偶数を出してしまうことも起こりうるわけです。
つまり、前回書いたような乱数発生器にも同じことが言えます。
1つの乱数発生器をアイテム出現率の他に、例えばアイテムの種類を選ぶための乱数としても利用していたとすると、アイテム出現の成功となるような値が発生する機会をアイテムの種類を選ぶ乱数を発生させる機会に奪われて、望まれる確率よりも低い出現率になってしまうこともあるわけです。
もしプログラマが乱数をこのような間違った使い方をしてしまった場合には、確率の数値はまるで当てになりません。そして、こういった理屈を理解していないプログラマが結構いるんですよね…。私も結構長い間、理解せずにやってましたしw
MHP は…どうなんでしょうね。ちゃんとわかってるプログラマが作ってるんでしょうか。前回書いたような要因もあるので、プレイしていても開発者の想定通りなのかどうかはまったくわかりませんw
2011年1月5日水曜日
確率の誤解 - 50/100 ≠ 500/1000
ここ最近、ずーーーっとモンハンばっかりやってました。
そう、先月発売した MHP3 です。
ようやっとレイア希少種を倒したのですが、もう今の装備では限界…。新しく装備を調えるために、レア素材求めてまた乱獲しなくっちゃなぁ…。
ということで、確率について少し語ってみます。
モンハンの攻略を乗っけてるサイトなんかを見てると、アイテムの手に入る確率なんかが書いてあります。たいていパーセントによる表記ですが、これってどうやって算出した値なんですかね?ざらっと見た限りではどうやら直接データを参照したもののようですが、おそらくプログラムの解析はやってないかと思います。
ともあれ、この数値に関しては、確率 50% もあるのに全然手に入らないだとかの文句がよく見られます。さて、この 50% という数値は果たして「100分の50」なんでしょうか。それとも「1000分の500」なんでしょうか。どちらも同じじゃないか、と思われるかもありませんがさにあらず。どんな単位で扱うかによって大きな違いがあるのです。
コンピュータで扱われる乱数はたいていの場合疑似乱数です。疑似乱数というのは、その名の通りでたらめな数値ではなく、でたらめな数値に見えるような数値を計算しているわけです。そして、質のいい乱数には条件があって、その一つに数値のばらつきというものがあります。例えば0~9の数値を発生させる乱数発生器なら、0~9までのそれぞれの数値が同じぐらいの頻度で出現する必要がある、ということです。まあ、一般的な乱数発生アルゴリズムならたいていこの条件はクリアしています。
さて、次に問題になるのはこの発生の周期です。先の例で言うと、例えば「1」という数値が出現してから何回乱数を回せば再度「1」が出現するか、という問題です。(知っている方に言っておきますが、ここで言う周期はアルゴリズムによる限界の周期ではなく、あくまでも目的の数値が出現する頻度の周期です。) だいたい10~20回程度で出現してほしいところですが、そうでない場合もあります。例えば乱数を100回発生させて開始から90回「1」が出現せずに最後の10回に連続して「1」が出現したとしても、先に言った出現頻度の条件はクリアしていますから仕様上問題ないことになります。
まあ、一般的な乱数発生アルゴリズムならここまで極端に偏ることはまずありません。しかし、前に言ったような「100分の50」あるいは「1000分の500」といった単位で考えるとこういった偏りが発生する可能性が非常に高くなります。
「100分の50」で考えると、乱数発生器が「0~99」までの数値を発生させるとして、「0~49」ならアイテムが出る、「50~99」ならアイテムが出ないとすると、100回乱数を発生させるうち最初の10回でアイテムが出ない可能性は決して低くありません。なぜなら乱数発生器が保証するのは「0~99」の各数値が出現する頻度がだいたい同じということであり、どんな順番で出現するかはいっさい保証していないからです。このため、確率が「100分の50」であったとしても、10回やって5回アイテムが手に入るとは限らないわけです。
そしてさらに「1000分の500」となるとこの可能性はさらに高くなります。先の「100分の50」なら最悪のケースでも51回やれば確実にアイテムが1つ手に入ることになりますが、「1000分の500」となると最悪の場合501回目までアイテムが手に入らない可能性があることになります。
つまり、「100分の50」と「1000分の500」とでは確率に大きな違いがある、と言えるわけです。
なので、攻略ページに出現率1%とか書いてあっても、実際プログラム側でどんな単位で扱っているかがわからなければ、手に入るまでに必要な回数もわからないということです。逆鱗とかけっこうな個数必要だったりするですが…いつになったら集まるかなぁ… orz
他にも、多くのプログラマが勘違いしている乱数の扱いがあるのですが、それはまた今度。
そう、先月発売した MHP3 です。
ようやっとレイア希少種を倒したのですが、もう今の装備では限界…。新しく装備を調えるために、レア素材求めてまた乱獲しなくっちゃなぁ…。
ということで、確率について少し語ってみます。
モンハンの攻略を乗っけてるサイトなんかを見てると、アイテムの手に入る確率なんかが書いてあります。たいていパーセントによる表記ですが、これってどうやって算出した値なんですかね?ざらっと見た限りではどうやら直接データを参照したもののようですが、おそらくプログラムの解析はやってないかと思います。
ともあれ、この数値に関しては、確率 50% もあるのに全然手に入らないだとかの文句がよく見られます。さて、この 50% という数値は果たして「100分の50」なんでしょうか。それとも「1000分の500」なんでしょうか。どちらも同じじゃないか、と思われるかもありませんがさにあらず。どんな単位で扱うかによって大きな違いがあるのです。
コンピュータで扱われる乱数はたいていの場合疑似乱数です。疑似乱数というのは、その名の通りでたらめな数値ではなく、でたらめな数値に見えるような数値を計算しているわけです。そして、質のいい乱数には条件があって、その一つに数値のばらつきというものがあります。例えば0~9の数値を発生させる乱数発生器なら、0~9までのそれぞれの数値が同じぐらいの頻度で出現する必要がある、ということです。まあ、一般的な乱数発生アルゴリズムならたいていこの条件はクリアしています。
さて、次に問題になるのはこの発生の周期です。先の例で言うと、例えば「1」という数値が出現してから何回乱数を回せば再度「1」が出現するか、という問題です。(知っている方に言っておきますが、ここで言う周期はアルゴリズムによる限界の周期ではなく、あくまでも目的の数値が出現する頻度の周期です。) だいたい10~20回程度で出現してほしいところですが、そうでない場合もあります。例えば乱数を100回発生させて開始から90回「1」が出現せずに最後の10回に連続して「1」が出現したとしても、先に言った出現頻度の条件はクリアしていますから仕様上問題ないことになります。
まあ、一般的な乱数発生アルゴリズムならここまで極端に偏ることはまずありません。しかし、前に言ったような「100分の50」あるいは「1000分の500」といった単位で考えるとこういった偏りが発生する可能性が非常に高くなります。
「100分の50」で考えると、乱数発生器が「0~99」までの数値を発生させるとして、「0~49」ならアイテムが出る、「50~99」ならアイテムが出ないとすると、100回乱数を発生させるうち最初の10回でアイテムが出ない可能性は決して低くありません。なぜなら乱数発生器が保証するのは「0~99」の各数値が出現する頻度がだいたい同じということであり、どんな順番で出現するかはいっさい保証していないからです。このため、確率が「100分の50」であったとしても、10回やって5回アイテムが手に入るとは限らないわけです。
そしてさらに「1000分の500」となるとこの可能性はさらに高くなります。先の「100分の50」なら最悪のケースでも51回やれば確実にアイテムが1つ手に入ることになりますが、「1000分の500」となると最悪の場合501回目までアイテムが手に入らない可能性があることになります。
つまり、「100分の50」と「1000分の500」とでは確率に大きな違いがある、と言えるわけです。
なので、攻略ページに出現率1%とか書いてあっても、実際プログラム側でどんな単位で扱っているかがわからなければ、手に入るまでに必要な回数もわからないということです。逆鱗とかけっこうな個数必要だったりするですが…いつになったら集まるかなぁ… orz
他にも、多くのプログラマが勘違いしている乱数の扱いがあるのですが、それはまた今度。
登録:
投稿 (Atom)