MASATOの開発日記 2001年4月


5/20(NEW)

最近データーファイルの形式に悩んでいる。 例えば、RPGの敵データー、例えば、シューティングゲームのステージデーター あるいは家計簿データー、はたまた日記のデーター。 色々データーはあるのは良いのだが、それぞれ別のフォーマットだと、 読み込み/書き込みルーチンを書かなくてはならない。 これがまた結構面倒なので、たった一つのフォーマットで全部書きたい。 とすると、既にどこのだれかが万能フォーマットを用意してくれていそうだ。 と考えてみると、あった。あれだ。XMLだ。 というわけで今現在はXMLにはまり中。VBScriptやJavaScriptでも扱えるところが良い。 開発ツールが無くても全部メモ帳で事足りる。 もちろんXMLはVC++でもVB++でも扱える。Javaでも扱えるらしい。 さらに深みにはまっていったらVC++でXMLとかいう記事が出てくるかもしれない。

(Visual C++)CStringをLPTSTRにキャストしてはいけない理由。

CStringは、

CString str = "Hoge";
LPTSTR data = (LPTSTR)(LPCTSTR)str;
// ●dataを通してstrを書きかえられる。●
data[0] = 'N';
としてLPTSTR型にキャストできる。つまりconstを外すことができてしまう。 非Unicode環境ならば、LPCTSTRはconst char*と同じで、LPTSTRはchar*と同じである。 こうやってLPTSTR型にすれば、CStringクラスをCの文字列の感覚で扱うことが出来る。 しかし、この感覚で扱うのは大変な間違いである。

例えば、次のプログラムを実行してみよう。

CString str1 = _T("Hoge");
CString str2 = str1;
LPTSTR data1 = (LPTSTR)(LPCTSTR)str1;
data1[0] = _T('T');
::MessageBox(NULL, str1, _T("str1"), MB_OK);
::MessageBox(NULL, str2, _T("str2"), MB_OK);
str1は"Toge"で、str2は"Hoge"となるように思えるかもしれないが、 実際にはstr2まで"Toge"となる。これは、CStringが、 メモリの節約と高速化のため、必要でないときは 文字列自体のコピーを作成しないからである。 つまり、str2の文字列はstr1の文字列と共用しているのである。 もし、SetAt等のCStringのメンバ変数を使って str1を書きかえれば、CStringはそれを感知し、 そのとき初めて文字列のコピーを作成してそれを書きかえる。 (そしてstr2は変わらずにstr1だけが変わる。) このように、CStringはLPCTSTRにキャストした後、 constを外されることを考慮されていない。 というよりもこれは考慮しなくて良い。 CStringに限らず、constを外すキャストは予想外の結果を招きかねない。 できる限り行わないようにしよう。

できる限りと言ったが、その中に入らないもの、 つまりは使わざるを得ない場合を紹介しよう。 どんな場合かというと、constを外しても、文字列を書き換えない場合だ。 文字列を書き換えないのになんでconstを外すのかというと、 本来ならばconst char*であるべきライブラリ関数の引数が、 何かの手違いや都合でchar*になっている場合である。
私は、この場合以外にconstを外すべき場合が思いつかない。 もしそのライブラリが自作のものならば、 ライブラリの方を修正し、引数の型をconst char*とするべきであろう。 だが、プログラムは自分のライブラリだけで動いているわけではない。
その典型的な例がSDKである。
例えば、LVITEM構造体を用いて、LVM_SETITEMメッセージを送るときである。 LVITEM構造体のメンバ変数pszTextは、LPTSTR型である。 よって、CString型の値をここに入れることは出来ない。 恐らくこの変数は、LVM_GETITEMメッセージを使うときのためにconstを外してあるのであろう。 しかし、LVM_SETITEMでは文字列を変更される恐れは無い。(おそらく) よって、この場合はLPTSTRにキャストしても良い場合である。 事実MFCのCListCtrl::SetItem関数でもconstを外している。
他にも、RegQueryValueEx関数の第2引数lpValueNameの型がLPTSTRになっている。 そして、この関数はこのlpValueNameで渡された文字列を変更したりはしない。(おそらく) 正直、私には何でこの引数の型がLPCTSTRでないのか分からない。 不可解ではあるが、CString型の値をこの関数に渡したいと思ったとき困るのも事実である。 こんなときこそconst外しキャストの出番である。
こういった、文字列は変更されないけれどLPTSTR型がどうしても 必要な場合のみconstを外すべきである。 決して文字列を変更するためにconstを外してはならない。


masato@mb.kcom.ne.jp