C++ 可変長配列メンバ 書いて覚えるための初心者自己中記事
構造体の話。
ん、わかりにくいな。
構造体でファイルのサイズとパス名を保存しておく場合に、
struct Fileinfo { int sizeFile;//ファイルサイズ char path[MAX_PATH];//パス名 };
こうすればいいけど、パス名を常に最大まで使うわけじゃない。
ほとんどがだいぶ余る。
なので動的配列にする。
struct Fileinfo { int sizeFile;//ファイルサイズ int sizePath;//パス名のサイズ char* path;//パス名 };
これはあれか、呼び出すときにはsizepath の数値分をpathから呼び出すということか。
で、ここからが本題だな。
この構造体の内容をファイルに保存したい場合。
パス名path はポインタだからアドレスが保存される。パス名じゃなくアドレス。
よろしくない。
解決案の一つでoffsetofマクロを使う。
※write(アドレス, 出力するバイト数)
//file.openしてるとして //FileInfo infoもあるとして file.write((const char*)&info, offsetof(FileInfo, path)); file.write(info.path, info.sizePath)
?????
ちょっと理解できないです。
えっと、テキストだと
まずsizeFileまでのデータを保存して、次に確保したメモリの内容を保存する、と書いてる。
struct Fileinfo { int sizeFile;//ファイルサイズ int sizePath;//パス名のサイズ char* path;//パス名 };
まず、理解できないのが最初の(const char*)&info
これはなんだ?
はっ!テキストの序盤に書いてあった。
writeの第一引数はconst char* だからキャストしないといけないのか。
で、そのあとに第二引数がバイト数なのでFileInfoからpathまでのバイト数を出してるのね。
二番目のwriteは、info.pathだ、char*だもんね。で、サイズがsizePathね。
良かった、理解できたポイ。
でも、これはいまいちらしい。
メンバ変数にポインタを使わなければwrite1回で出来るのにって書いてる。
んで、こんなの出てきた
//構造体のオブジェクトを動的に確保して多めのサイズにする struct Fileinfo { int sizeFile;//ファイルサイズ int sizePath;//パス名のサイズ char path[1];//パス名 };
//FileInfo* info; file.write((const char*)info, offsetof(FileInfo, path) + info->sizePath);
疑問がたくさん。
まず、write の部分で第二引数、さっきこんな感じで足せばよかったのでは?
どうして今回はオブジェクトがポインタになったんだ?
それと、構造体のオブジェクトを多めに確保って、それこそ無駄なのでは?
あ、そうか。メンバ変数がポインタだったときはポインタのアドレスが入ってるんだった。1と3の疑問は解決。
オブジェクトがポインタになった理由はなんだ?
一晩たったがわからん。
とりあえず、これを実現したコードがどばっと。
また、一つずつ考えよう。
struct FileInfo { size_t sizePath;//パス名のサイズ size_t sizeFile;//ファイルサイズ char path[1];//パス名 //動的確保された領域のアドレスを保持して管理するクラス class Holder { public:
private: FileInfo* m_info; }; };
まず構造体、これはさっきの通り。
sizePath がパス名path の実際の大きさを表すのか。
名前が個人的に覚えにくいけど、じゃあどんな名前がいいのかと言われると難しい。
そして構造体FileInfoのメンバとしてクラスHolderがある。
構造体FineInfoの中にあるクラスHolder のメンバ変数は構造体FileInfoのポインタm_info。
次からはクラスHolderの中身。
次。
//パス名のサイズから構造体全体のサイズを計算 static size_t CalcSize(size_t sizePath) { return offsetof(FileInfo, path) + sizePath; }
静的メンバ関数Calcsize サイズ計算
sizePath(パス名のサイズ)を引数にする。
offsetof関数で構造体FileInfo のpathの先頭アドレスまでのバイト数を出す。
それに、sizePath(パス名のサイズ)を足す。
構造体の最後に入っているクラスHolderはないものとするのだろうか?
次、
//パス名のサイズをもとにオブジェクトを作成 void Init(size_t sizePath) { m_info = (FileInfo*)new char[CalcSize(sizePath)]; m_info->sizePath = sizePath; }
sizePath(パス名のサイズ)をもとにCalcSize関数で構造体のサイズを求めたら、そのサイズでchar型をnewする。そのままキャストでFileInfo* にしてHolderのメンバ変数FileInfo* m_info に渡す。
m_infoは構造体FileInfoのポインタなのでそのメンバsizePath(パス名のサイズ)に引数で渡ってきた(構造体のサイズを求める根拠になった)値を入れる。
次。
//データを指定して構築 Holder(size_t sizeFile, const char* path) { size_t sizePath = strlen(path) + 1; Init(sizePath); m_info->sizeFile = sizeFile; strcpy(m_info->path, path); }
ようやくコンストラクタ。
Holderオブジェクトが作られるときに引数でsizeFile(ファイルのサイズ)と文字リテラル(const char* path)が送られてくる。
sizePathという変数を用意して、引数の文字列リテラルの文字列の長さをsize_t型で取得し、貰う。
さっき確認したInit関数に文字列の長さを入れて呼び出す。
これで、メンバ変数m_infoに必要なアドレス領域が確保されて、m_info->sizePathにも値が入る。
そのご、同m_info->sizeFileに引数で渡ってきたファイル名を入れる。
そして、strcpyでm_info->pathに引数の文字列をコピーする。
これでHolderメンバ変数m_infoに情報が入った。
ちなみにstrcpy使うとエラーになる。strcpy_sを使えとでる。
//データを指定して構築 Holder(size_t sizeFile, const char* path) { size_t sizePath = strlen(path) + 1; Init(sizePath); m_info->sizeFile = sizeFile; strcpy_s(m_info->path,m_info->sizePath, path);//変更しました }
つぎ、
//データを指定せずに構築 explicit Holder(size_t sizePath) { Init(sizePath); }
データを指定しないバージョン。
データとはパス名とファイルサイズ。
それを指定しない場合はsizePath(パス名のサイズ)を引数にする。ってことは、まだ入れる情報決まってないから任意の幅でメモリ領域を確保して、って事か。
sizePathだけが入っていてsizePathの大きさに合わせたm_infoが作られた状態。sizeFile(ファイルのサイズ)とpath(パス名)は後で入れる。
次。
//アドレスを(const)char* で取得 char* GetAddress() { return (char*)m_info; } const char* GetAddress()const { return (const char*)m_info; }
m_infoのアドレスをchar* で取得する関数。
これはあれか、write関数で使うためにchar* なのか。
次、
//メモリを解放 virtual ~Holder() { delete[] GetAddress(); }
デストラクタ。
deleteはnewで作ったアドレスを解放するからm_infoのアドレスを指定するのね。
次、
//FileInfoオブジェクトのサイズ取得 size_t GetSize()const { return CalcSize(m_info->sizePath); }
これはそのまま。
これでクラスHolderは終わり。このクラスは
//動的確保された領域のアドレスを保持して管理するクラス class Holder {
なので、ファイルへの書き込みとかは別。
つぎはそれ関係。
//入出力を行うファイルの名前 const char FILENAME[] = "fileinfo.dat";
まず入出力を行うファイルの名前を定数に。
次、
//出力 void Output() { ofstream file; file.open(FILENAME, ios::binary); if (!file.is_open()) { return; } //FileInfo構造体の作成 const FileInfo::Holder info(42, "Hoge.txt"); //FileInfo構造体の出力 file.write(info.GetAddress(), info.GetSize()); }
ofstreamクラスだと出力に限定されるんだっけ。
ファイルを開いた。
あと、出力するためにとりあえずオブジェクトを作っている。構造体FileInfoの中のクラスHolder のオブジェクト作成。Holderクラスのコンストラクタにファイルサイズとパス名を渡しているから、全部の情報が入って必要なメモリ領域が確保されているm_infoが作られているはず。
writeにアドレスとサイズを渡して出力している。
つぎ、
長いから分割する。
//出力 void Input() { ifstream file; file.open(FILENAME, ios::binary); if (!file.is_open()) { return; } //FileInfo構造体のサイズを読み、ファイルポインタを戻す size_t sizePath; file.read((char*)&sizePath, sizeof sizePath); file.seekg(-(sizeof sizePath), ios::cur);
ifstreamクラスだから入力。
ファイルを開く。
size_t型の変数sizePathを用意。
ここから理解できない。
read関数の引数は受け取るアドレスと受け取るバイト数のはず。
さっき作った変数sizePath を第一引数にするのはわかるけど、初期化してない状態のsizePathをsizeof で第二引数にもするのはどういうことだ?
そのあとseekgを使ってsizePath分戻っている。
この行はエラーになるし。
進んだらわかるかもしれない。
とりあえず次、
//FileInfo構造体の作成 FileInfo::Holder info(sizePath); //FileInfo構造体の入力 file.read(info.GetAddress(), info.GetSize());
ここでデータを受け取る構造体オブジェクトをさっきのsizePathで初期化・・。
read関数で入力するオブジェクトのアドレスと読み込むサイズを指定。
//ファイルから読みだした情報を出力 cout << "sizePath : " << info->sizePath << endl << "sizeFile : " << info->sizeFile << endl << "path : " << info->path << endl;
表示。
ちょっとまて、これで終わっちゃった。
//FileInfo構造体のサイズを読み、ファイルポインタを戻す size_t sizePath; file.read((char*)&sizePath, sizeof sizePath); file.seekg(-(sizeof sizePath), ios::cur);
これがやっぱりわからないぞ。
FileInfo構造体のサイズを読み、って書いてるけど、
あ、
//ファイルの情報を保持する構造体 struct FileInfo { size_t sizePath;//パス名のサイズ size_t sizeFile;//ファイルサイズ char path[1];//パス名
最初にあるのがsizePathか!
まじか・・・。これ普通の人は楽勝な感じなのか?
分からな過ぎて全然進まなかった・・・。
とりあえず、コードは理解できたけど、全体像が脳内でぼやけてるな。
構造体FileInfoがある。
その中にクラスHolderがある。
全体的に構造体のサイズを出すときとかにクラスHolderの存在がでてこなかったと思う。
クラスには構造体FileInfo型を指すポインタm_infoがある。
newで作った構造体はm_infoがアドレスを持つようになる。
これはHolderのオブジェクトを作ってからやること。
Hloderのオブジェクトを作ったときにオブジェクトの中にあるm_infoが構造体のアドレスを持っている。そのなかにはクラスHolderのメンバでm_infoのポインタがあるけどそれはNULLなのか。なのか?
構造体の中にクラスを作った理由は何だろう。
うん、構造体からクラスだけ取り出して別々にコード書いてもちゃんと動く。
これはあれか、テキストだからいろいろなのを見せてくれているだけなのかな?
結構大変だった。
ここまで。