記憶領域量、百分の一の時代から
以前 wxWidgets を使ったことがあって1、最近また使ったときに一番驚いたというか感慨深かったのが次の一文でした。
the
wxT()
macro was not necessary any more and was declared obsolete.
そういう時代になったんですねぇ。というわけで先にこれにどう対処したかという報告を。それから関連する歴史を知ってる限りで記します。
過去の資産を活用するために
wxNO_IMPLICIT_WXSTRING_ENCODING を定義してみた
先述の「また使ったとき」というのは szmCt; 翻訳対応 openMSX ランチャー (catapult/wxcatapult 派生作品) のこと。タイトル通り openMSX の catapult から派生した作品です。
先の英文が記載されていた Implicit and explicit encoding of wxString data や Safer S... は、どちらかというと wxNO_IMPLICIT_WXSTRING_ENCODING
を使わせたい記事でして、wxT()
が不要な話は副次的なもののようです。ということで、このマクロを catapult のコードに適用してみました。
…当たり前にエラーが山ほど出てきます。wxString var = wxT("string");
といったところをすべて auto var = wxString::FromUTF8("string");
で置き換えればいいのでしょう。
接尾辞関数で置き換えた2
とはいえ、wxString::FromUTF8
はちょっと長くて無数に出てくるのは憚られます。なので次のような関数を定義しまして、全部 "string"_o
に置き換えました。_o
を選んだ理由はなんかあったはずですが忘れた。
wxString operator"" _o(const char* ptr, std::size_t len)
{
if (len) return wxString::FromUTF8(ptr, len);
return wxEmptyString;
}
普通の関数でももちろんいいのですが、引数に std::size_t
があるのでコンパイル時点でコンパイラが長さを把握している感が良いのです。
wxrc が出力する .cpp ファイルで引っかかる
wxWidgets 公式が勧めているにもかかわらず、.xrc
リソースを .cpp
に変換できる wxrc というツールが wxNO_IMPLICIT_WXSTRING_ENCODING
に対応していません(執筆時現在)。
出てきた .cpp
ファイルの中身も同じように置き換えればいいのですが、GUI リソースの変更に合わせて自動的にやってくれないと困ります。なので変換スクリプトを別に用意して、wxrc が出力したファイルに自動的に修正をかけるよう CMakeLists.txt に記載しました。ここにその部分を載せようと思ったんですが、注釈が山ほど必要になるんで諦めました。どうしても見たいという方は szmCt をお買い求めください。
さて、ここから下は雑なお話。
私が知る範囲の歴史など
経緯
C++と文字列
歴史的な経緯から、C++ は今でも文字回りのあれこれが実装依存になっています。ようやく C++20 でUTF-8エンコーディングされた文字の型として char8_t を追加、今後の C++23 で汎用的なソースコードのエンコーディングとしてUTF-8をサポートすることになったそうです。
逆に言うとそれまでは、というか現在でも、C++単体では文字の扱いが実装依存のままなのです。
wxWidgetsと文字列の過去
wxWidgets では初期も初期、1997年のα版3から独自型 wxString
や上述の wxT
マクロなどで実装依存に対処してきた模様。char と wchar_t のどちらが良いかは環境次第だったので、wxT("some string")
と書いておけば適宜 "some string"
か L"some string"
に変換してくれたというわけです。
人力作業は減らす方がいい
で、それもうやんなくていいよと。いろいろあって4、wxString some(wxT("text"));
なんて人力に頼らず wxString some("text");
とだけ書いたら5あとはコンパイラ任せでええやないの、てことになったんだと思います。
もっと楽しようぜ
C++ から C 言語の API を呼び出す機会は数多くあります。普段 std::string
に文字列を持たせて、引数が char* の関数を呼ぶときは c_str()
を渡す形になります。
wxString も同様です。細かいところは少し違っていて char_str()
が char* 専用、wchar_str()
が wchar_t* 専用みたいな感じになっています。ほかにも似たようなのがいろいろあるので、当初からそういう様相ではなかったかもしれません。
ところでここでも、コンパイラ任せでいいんじゃない?という考えが当然出たのでしょう。実は char_str() や wchar_str() を使う必要はなく、直接 wxString を渡せば勝手に変換するようになっています。ついでに std::string や std::wstring の自動変換もできるようになっています。
やりすぎちゃった☆
ところが先に紹介した Safer S... の記載によると、wxWidgets 3.0 で Unicode ベースのビルドを標準化・単一化した結果、自動変換の数々が危険を呼び込むことになってしまったとか。それまでもちょっと危なっかしかったですが、3.0 以降でより危険が増えたようです。
そこで、数々の便利自動変換を抑制するため wxNO_IMPLICIT_WXSTRING_ENCODING
が用意されました。こちらを使うと wxT()
は意味をなくしてしまいます。元が char* だった場合、何らかの形でエンコーディング情報を与えなくてはならないので wxT()
を使う利点が失われてしまうのです。
便利さと危険度のトレードオフだったところ、トレード自体の評価が変わってしまった。そういうお話。
時代背景
最後にトレードの評価が変わったその他の理由などを。
2000年前後の状況
wxWidgets6 は Windows用に 95/98/Me 用の ANSI ビルドと 2000 用の Unicode ビルドを用意しました。Linux でも日本語なら EUC-JP 環境が当たり前で UTF-8 はオプション扱いのディストリビューションが多かったはずです。つまりこちらでも Unicode ビルドとそうでないものが用意されました。
UTF-8, UTF-16 が定義されはしましたが、前者では日本語文字部分が1.5倍の大きさになりますし、後者では特に英語圏で単純に2倍の容量が必要となるということで、必ずしも Unicode が喜ばれたわけではありませんでした。
ウィキペディアからそのまま引きますけども、Windows Me の動作要件がメモリ32MB、ハードディスク250~490MB。Windows XP でも 128MB/1.5GB。単位間違えないでくださいね。それぞれ 0.032GB, 0.00025TB~0.00049TB, 0.128GB/0.0015TB ですよ。…えーと、細かいのはおいとくとして、桁ちゃんと合ってる?
Linux なんかはそれ以下も対象としてましたでしょうから、いよいよ文章の記録に1.5倍やら2倍やらの容積をとるのは許しがたいという意見も当然あったのです。記憶域容量百分の一はフカシでもなんでもありません。ここでは触れてませんけど、処理速度だって大体そういうことです。
当時 UTF-8 の仕組みを利用して、日本語部分は Shift-JIS と同じ 2 バイトにするエンコーディングを考えて公開してた人いたんですよ。あのページ、保存しとけばよかったな。今はなきジオシティーズにあったと思います。
2025年の今
それから四半世紀が過ぎ、95 系と 2000 系を統合した Windows XP がサポート終了、Linux も EUC を使ったものが見当たらなくなり、どっちを向いても UTF-8 前提で動作しているものばかりになりました。
BSD と Mac の話がない? そりゃ当方、半世紀生きていたうち 15 年以上引きこもりでしたし、昨年は介護離職したけど介護する必要がなくなってまた引きこもりしてますもん。詳しい人はよろしく。名前すら出てない OS の話も。
それはさておき。
ハードの性能向上に合わせ、2000年頃には考えられなかった容量・解像度の動画が無数に飛び交う時代になって、UTF 採用による文章サイズの増加なんて誤差の範疇になっちゃったわけですわ。そりゃ互換性が重要になる Windows 以外は全部 UTF-8 ベースに移行しちゃう。Windows は UTF-16 ベースで。
かくして、2013年の wxWidgets 3.0.0 の公開とともに Unicode ビルドが標準・基本のビルド方法になったのでありました。え、それでも12年前の話なんです?
セキュリティの考え方自体も変わった
超ざっくりとしか見ていませんけども、wxWidgets 自身、おかしな使い方をされたらなるべくエラーを吐き出すようにがっつり手を入れられているところのようです。
例えば sample フォルダには相変わらずマクロを使ったイベントテーブルベースのコードが並んでいますけども、公式には wxEvtHandler::Bind
とかを使うのがお勧めされてます。イベントハンドラの引数が間違ってたらちゃんとコンパイルエラーで止まるんすよ、Bind。マクロではそうはいきません。現に間違ってましたからね、catapult。
wxString 周辺も、引数がおかしい場合はなるべくコンパイルエラーが出るようになったみたいです。ユーザー任せにするのはなるべく減らそうということなんだと思います。
書いてみた感想
思いのほかおまけパートのが長くなっちまった。
※公開後の修正は現在Typo分のみです。
-
ユーザー定義リテラル。 C++11 から規格化。 ↩
-
docs/changes_30.txt の一番下、Alpha 1, 5th April 1997 のところに wxString の記載があります。wxT マクロはいつからか不明ですが、相当以前からなのは間違いないでしょう。 ↩
-
C++の規格がコンパイラベンダーとの共同作業になり、あらゆるハードウェアが桁違いに性能を上げ、開発環境ソフトウェアも劇的な進化を達成したので。 ↩
-
この手法で安心して使えるのは ASCII 範囲の文字だけで、範囲外の文字から変換するには従前通り
wxString::FromUTF8
などの専用関数を使います。もっともこの使い方の場合、最初からwxT
は使ってないはずです。 ↩ -
当時は wxWindows という名前でしたが、それはまた別のお話。 ↩