CSVデータテーブルをプログラム上で使えるようにする前章で、Excelなどの表計算ソフトに記述したデータテーブルをCSV出力しました。この章ではそのCSVをプログラムで読み込み、プログラム上で扱えるようにします。Tableクラス一つのCSVデータテーブルを扱うクラスをTableクラスとしておきます。前章のCSVデータはこんな感じで:CSVデータテーブル(test.txt)63id,name,attribute,price,attack,defenceitem_w_0001,木刀,weapon,100,10,0item_w_0002,銅の剣,weapon,500,20,0item_w_0003,鉄の剣,weapon,1200,35,01行目は列数(項目数)、2行目は行数(レコード数)です。3行目は項目名で、これは後で値を取り出すためのキーとなります。テーブルルールとして、1項目目(id)がレコードを検索するキーとして使われるとします。Tableクラスを実装する前に、実際にTableクラスを使って上の項目にアクセスする部分を考えてみます。test.txtファイルを開いてCSVを解析しデータをメモリに格納します。次に1項目目のidにある名前、例えば「item_w_0002」を指定します。これでこの1行(レコード)を取得できます。次にそのレコードに対して「name」等と名前でアクセスすると「銅の剣」が得られます。table["item_w_0002"]["name"];テーブルを登録して、配列演算子で即アクセス!これだと簡単です。今回はこれを目指します。Tableクラスのコンストラクタでは開くべきファイル名を渡します。コンストラクタの中で解析は終わらせてしまいます。配列演算子は2次元配列のようになっていますが、実際には最初の["item_w_0002"]でレコードクラス(Record)のオブジェクトを取得しています。Recordクラスも配列演算子を持つため、続けて["name"]でレコードのname項目にアクセスできます。Record::operator[]が返すのは文字列だけとは限りません。整数(int)と浮動小数点(float)は欲しい所です。データテーブルをみるとそういうデータが混在するのがわかると思います。そこでそれらのデータをまとめて扱う簡易なVariant型を定義しておきます。key);};Tableクラスはrecordsというマップを持っています。このkeyにデータテーブルの1項目目(id)が来て、2項目目以降がvalueとしてRecordに格納されるわけです。マップのvalue引数が「Record*」とポインタになっているのに注目です。これは実体にしてしまうとコンテナ管理中に激しいディープコピーが起きてしまうためです。テーブルは巨大なのででかいコピーが起こるとまずいためポインタにしています。値をポインタで保持するという事は、レコードが登録される度にヒープからnewする事になります。この寿命はTableオブジェクトが無くなった時なので、デストラクタで削除します。それ自体は難しくありませんが、怖いのがTableのコピーです。Tableをコピーするとrecordsマップは共有されますが、デストラクタは2回呼ばれます。1回目で消えた後、2回目でさらに消そうとするため、メモリ保護違反で落ちます!そのためTableオブジェクトは「ディープコピーを禁止」しなければならないんです。そのため、Tableクラスのコピーコンストラクタと代入演算子がprivate実装になっています。これはNoncopyableの実装です(Noncopyableについてはクラス構築編「コピー禁止を徹底させるNoncopyableクラス」を参照下さい)。TableクラスにstaticなRecordオブジェクト(nullRecord)が一つ設けられていますね。これは、配列演算子でのアクセスでNullアクセスを防止するために設けています。table["hoge"];と未登録のキーhogeでRecordを取得しても戻り値がnullRecordならば値は適当ですが実体は保障されます。キーが存在するかチェックするメソッドを設けるのも得策です。*records[key];}コンストラクタがほぼすべてです。コンストラクタではファイルをオープンして1行ずつ読み込み値を取り出しています。実装内にContextというクラスが出てきます。0;}};本当に愚直にCSVでセパレートしているだけです。getAndNextメソッドを呼び出すと今の文字位置から次のコンマまでの文字列を切り出して返すと同時に文字ポイントを次の位置にセットし直します。これでgetAndNextメソッドを呼び出す度にCSVデータが1データずつ取り出せるわけです。Table::Tableコンストラクタ内のデータ取得部分でこのクラスが大いに活躍してくれます(^-^)Tableクラスのコンストラクタを通ると、CSVデータテーブルはすべてメモリ上に乗ります。後はそのアクセスメソッド(配列演算子)を実装するだけです。上記にあるように実装は極めて簡単ですが、keyが無かった時にnullRecordへの参照を返すのが特徴ですね。Recordクラス1レコードを表すRecordクラスはCSVデータテーブルの2項目目以降の値を保持します(1項目目はTableクラスのrecordsマップのKey)。val;}Recordクラスは登録用のaddメソッドと値を取り出す配列演算子しか主たる所はありません。Variant*>と、項目名をキーとしその値へアクセスするマップです。これもVariant*とポインタになっています。理由はTableの時と同じです。登録と取得はmapへ値を登録したり取得したりするのをラップしているだけですが、ちょっと注意が必要です。addメソッドの実装を見て下さい。登録時同じキーがあった場合、Variantポインタ変数を上書きすると前のVariantポインタ先がリークしてしまいます。そのため、同じキーがあった場合は実体を上書き変更してしまっています。取得については未登録なkeyを指定した時にnullVariantへの参照を返しています。これもTableクラスの時と同じですね。Recordはこんなもんです。Variant構造体Variant構造体は整数(int)、浮動小数点(float)そして文字列(std::string)を保持してレコード内の1データ情報を表現する値クラスです。実装はすでに�@にあります。table["hoge"]["foo"].iVal」のように対応するメンバを指定しなくて良くなります。値を入れる方法は直接入れても良いのですが、CSVを解析した結果は必ず文字列になってしまうため、文字列を引数にとるコンストラクタを設けました。べたな方法ですが楽はできます(^-^)Tableクラスとそれに関連するクラスの実装は以上です。シンプルにできますが、これで大量のデータが格納されているテーブルから必要な情報をガンガン取得できるようになるわけですから中々の前進です(^-^)/Tableクラスについてはサンプルプログラムにて公開致しますので、ご自由にお使い下さい。1データ配列について今回のデータテーブルは実質2次元配列です。STGで敵が爆発した時に毎回同じSEではなくて何種類かの中からランダムに選ばれて鳴らしたいという事がある場合、「ExplosionSE」という項目の中に複数のSE名を並べたくなります。1データ内が配列になっているわけです。Tableクラスでこれを実現する場合、CSVの書式拡張とパーサーの拡張が必要になります。この拡張は有効ではありますが、Excel側で配列部分を手書きする必要が出てきます。Excelは1セルを分割出来ないからです。個人的には1データ配列は冗長かなぁと思っていますが、テーブルサイズを節約したい時には必要になるかもしれません。無くても何とかなります(^-^;。