堕天使の煉獄
2018-10
04
07:36:18
遅々として
ここ最近はダンジョンRPGゲーム開発用のエディタ作りをやっていたのですが。
ちょっとエディタの作りの方も飽きてきたのでちょっと転進。
実際のゲームPGのほうのオレオレライブラリ作りの組み直しにとりかかる。
ずっとこっちに触れていない間、ずっと文字コード周りをどうするかというのを決めかねていたりで。
状況的にはあんまりかわってないんですよね。いまだにchar8_tが追加されないし。
コード自体の文字コードがutf8というのはもはやスタンダードなので良いとして、コード内の文字列リテラルをどうするのか。プロジェクト内での内部文字コードを何にするのか。
いまのところ、実行環境次第でも変化するものなので「明確な正解はない」というのが実情で。
とはいえ、明確な正解が出てくるまで待っているわけにもいかないので、なんとか将来的な変更も予想したりしながらやりくりするしかないぽ。
とりあえず今のところの方針は。
vc++環境でターゲットはwindowsという前提で。
■vc++文字コードセット設定は「unicode」を使用。
「マルチバイト」の方はshift-jis前提なのでもはや過去の遺物。古いプロジェクトの保守用でしょう。
■TCHATおよびwchar_tは使わない
wchar_tは環境によって中身が変わるため(現行win=2byte、unix系=4byte)
文字コード周りはchar16_tやchar32_tなど環境依存でない物を使う。
が、後述の理由により、「表面的にwchar_tは使わない」で妥協
■文字列リテラルに_T("")マクロおよびL""は使わないでu8""で統一
TCHATと同じ理由で使わない方がよさげ。
専用のdxT("")などを用意して、環境に合わせて変える必要がある部分は対応。
L""はwin用専用コード内部でwinapiに直接渡す場合のみ利用。
■utf-8はあくまで受け皿
というのもutf8は文字列操作に向いてない形式。
向いてるのは外部から受け取ったりするときに文字化けし難い、受け取った文字はutf8だと判別しやすいという所。
■const char* std::stringの中身はutf8と想定
shift-jisはもはや過去の遺物なので使用しないのが望ましい。
中身がutf8だと明確にするにはchar8_tが必要なのだが現状ないのでしかたなくusing char8_t = charとかusing u8string = std::stringとか気持ち悪いけどやっておく方向で。
■utf8→内部コード→最終出力コードの流れで
コード内の文字リテラル、リソースファイル内の文字コードはutf8で統一。
画面出力の際に内部コードから最終出力コードに変換する。
■内部コードは……なんだかんだでutf16
文字列操作を考えると、utf16かutf32の二択か。
しかしwin上だと、char16_tとwchar_tはバイト列では同一になるとはいえ、reinterpret_cast使うのがなんとなく気持ち悪い……。
そしてstd::wcstolあたりの標準ライブラリにchar16_t対応版が無い。つかえないこともないけど毎度wchar_tにキャストの嵐に……。
その辺の事情はutf32も同じ。標準ライブラリの対応がまったく追いついてない。しかし文字列操作を全部自前で書くというのも現実的ではない。
てかwchar_tが4バイトになるunix系ではstd::wcstolとかどうなってるの??
unix系ではそもそも日本語はじめ多バイト文字を使わない前提なかんじで無視?
内部コードはutf32がよさげなのだけども(一文字あたりつねに4バイトも使うのはアレだけど)strtol系が自前で書かなきゃ行けない。
to_stringもwchar_tの場合std::wostringstreamとかつかうんだけどもこのストリーム系もutf16、utf32はまだ未対応。なのでutf16、utf32は「早過ぎたんだ……腐ってやがる……」と巨神兵状態……。まだ使い物になりません。
じゃあどうするかと言えば内部コードはutf16としながらも内部コードを扱う文字クラスの中ではwchar_tで管理することに。結局内部コードをもつ文字列クラスはwin専用クラスに……。将来的にはこの文字列クラスの中身を書き換えるだけでいろいろと変更に対応出来る感じに組むしかないな……。
■サロゲートペア問題は局所的問題として解決
内部コードは現状、文字変換の標準ライブラリが使えるのでwchar_t(=utf16)がベターだとおもわれるのだけどもutf16にはサロゲートペアというお荷物仕様が。
問題になるのは文字列分割の際に、その境界にサロゲートペアの文字が来たときの場合なので、ゲームPGでは文字を1文字づつぺろぺろだすメッセージウィンドウとかぐらいでしょうか。ゲームスクリプトの解析なんかはスクリプト用のコマンドは半角限定にすればサロゲートペアの影響は受けないですし。
サロゲートペア対応はそれに特化したメッセージウィンドウクラスの中でのみ対処して、文字列クラスのなかではサロゲートペアがらみの処理は一切省くかんじで良いと思われる。(文字コードの相互変換部分では必要だけど)
サロゲートペアの表示自体はDXライブラリとかのお仕事なので気にしない。
そんなかんじですすめてみるかーと。
で、各種文字コード変換がひつようになるのだけども。
標準ライブラリの文字コード変換のstd::codecvtはどうも実装がかなりおそまつでセキュアでもないってことでc++17で非推奨……。代替用意してから消えて欲しいものですw
てことでutf8←→utf16はwinapiでもあるのだけどもそれ以外のutf16←→utf32とutf8←→utf32がwinapiでは用意されていない。
ということでその辺自前で実装することに。
そこでutf8←→utf16の変換、自前実装とwinapiとでコスト的にどのくらい差があったりするものなのか……と気になったので計測してみたり。
utf8→utf16変換
結果の左は変換文字数を400文字 右は20文字
それぞれ10000回変換した結果。
MultiByteToWideChar(winapi)
debug = 280ms 210ms
relese = 50ms 14ms
自前変換utf8 → utf16
debug = 2370ms 300ms
relese = 54ms 12ms
ふむう。さすがwinapi。
独自最適化されてるせいか、デバッグビルドでもそこそこ速い。
一方自前実装の方はデバッグビルドだとクッソ遅いw
結果受け取りのバッファにstd::unique_ptr使ってるところで、リリースビルドだと生ポインタとほぼ同じコストになるけどデバッグビルドだとクッソ遅い。ってところで足を引っ張ってる物とおもわれる。
しかし何度か実行した平均値をとってきてるのだけども、少ない文字数の場合リリースビルドだと、微妙に自前実装のが速かったりする。文字数多い場合だとちょっと負けてるけど。
その辺、MultiByteToWideCharではやってる安全チェックのいくつかをすっ飛ばしてる部分の差が出てるのかなーとか。
そんな感じでutf8←→utf16変換に関しては、vc++上ではwinapiをつかって、win以外の方では自前実装のものを使う感じの運用にすることに。
あとは10000回変換してこの速度なら、毎度しょっちゅうコード全域にわたって文字コード変換してもたいしたコストじゃないんだなーやっぱり。とかおもたり。
QTのソースコードと互換性ほしいので、文字列リテラルはu8""で統一したいのだけども、毎度変換コストかかるのはどうなのかなぁとおもてたので。
QTに関してはGUIアプリ作成用なので変換コストとか気にすることはないのだけども、ゲームPGではそうもいなかかったりするし。
しかし何となく適当に多めの文字列ってところで適当な文字列をその辺からコピペしたのがだいたい400文字ぐらいだったのだけども。
400文字て普通の原稿用紙一枚分なんだよなと。
で10000回変換てことは原稿用紙10000ページ分てと、一般的な文庫本だと一冊400字詰め原稿用紙換算だと500ページぐらいらしいので小説本20冊分ぐらいか。
適当にぐぐってみたところ、一般的なビジュアルノベルの文章量てのは50万文字ぐらいとからしい(しっかりとボリュームある感じの場合? すくないのは20万文字とかいうデータもあったのでよくわからん)50万/400で原稿用紙1250ページ分とかんがえるとその約10倍の量のテキストを読み込んで(ファイル読み込みにかかる時間はこの際無視して)一括でutf8からutf16に変換してもリリースビルドなら50msしかかからないのかーと思うと、ゲームで使うテキストファイルも変換コスト避けるために内部コードと同じ文字コードに……なんてのもセコセコバッタの無駄な努力なのだなぁとかおもてしまったりw
無視出来るレベルのコストのために、無駄な面倒抱えるべきではないですね……。
そんな感じで、まだまだほんとにこれでいいのか? と模索しながらの文字コード周り。
いっそもうwin専用としてガッツり書いた方が楽なんだけどなーとか一瞬楽な方に流れそうになりながらガリガリと。
ちょっとエディタの作りの方も飽きてきたのでちょっと転進。
実際のゲームPGのほうのオレオレライブラリ作りの組み直しにとりかかる。
ずっとこっちに触れていない間、ずっと文字コード周りをどうするかというのを決めかねていたりで。
状況的にはあんまりかわってないんですよね。いまだにchar8_tが追加されないし。
コード自体の文字コードがutf8というのはもはやスタンダードなので良いとして、コード内の文字列リテラルをどうするのか。プロジェクト内での内部文字コードを何にするのか。
いまのところ、実行環境次第でも変化するものなので「明確な正解はない」というのが実情で。
とはいえ、明確な正解が出てくるまで待っているわけにもいかないので、なんとか将来的な変更も予想したりしながらやりくりするしかないぽ。
とりあえず今のところの方針は。
vc++環境でターゲットはwindowsという前提で。
■vc++文字コードセット設定は「unicode」を使用。
「マルチバイト」の方はshift-jis前提なのでもはや過去の遺物。古いプロジェクトの保守用でしょう。
■TCHATおよびwchar_tは使わない
wchar_tは環境によって中身が変わるため(現行win=2byte、unix系=4byte)
文字コード周りはchar16_tやchar32_tなど環境依存でない物を使う。
が、後述の理由により、「表面的にwchar_tは使わない」で妥協
■文字列リテラルに_T("")マクロおよびL""は使わないでu8""で統一
TCHATと同じ理由で使わない方がよさげ。
専用のdxT("")などを用意して、環境に合わせて変える必要がある部分は対応。
L""はwin用専用コード内部でwinapiに直接渡す場合のみ利用。
■utf-8はあくまで受け皿
というのもutf8は文字列操作に向いてない形式。
向いてるのは外部から受け取ったりするときに文字化けし難い、受け取った文字はutf8だと判別しやすいという所。
■const char* std::stringの中身はutf8と想定
shift-jisはもはや過去の遺物なので使用しないのが望ましい。
中身がutf8だと明確にするにはchar8_tが必要なのだが現状ないのでしかたなくusing char8_t = charとかusing u8string = std::stringとか気持ち悪いけどやっておく方向で。
■utf8→内部コード→最終出力コードの流れで
コード内の文字リテラル、リソースファイル内の文字コードはutf8で統一。
画面出力の際に内部コードから最終出力コードに変換する。
■内部コードは……なんだかんだでutf16
文字列操作を考えると、utf16かutf32の二択か。
しかしwin上だと、char16_tとwchar_tはバイト列では同一になるとはいえ、reinterpret_cast使うのがなんとなく気持ち悪い……。
そしてstd::wcstolあたりの標準ライブラリにchar16_t対応版が無い。つかえないこともないけど毎度wchar_tにキャストの嵐に……。
その辺の事情はutf32も同じ。標準ライブラリの対応がまったく追いついてない。しかし文字列操作を全部自前で書くというのも現実的ではない。
てかwchar_tが4バイトになるunix系ではstd::wcstolとかどうなってるの??
unix系ではそもそも日本語はじめ多バイト文字を使わない前提なかんじで無視?
内部コードはutf32がよさげなのだけども(一文字あたりつねに4バイトも使うのはアレだけど)strtol系が自前で書かなきゃ行けない。
to_stringもwchar_tの場合std::wostringstreamとかつかうんだけどもこのストリーム系もutf16、utf32はまだ未対応。なのでutf16、utf32は「早過ぎたんだ……腐ってやがる……」と巨神兵状態……。まだ使い物になりません。
じゃあどうするかと言えば内部コードはutf16としながらも内部コードを扱う文字クラスの中ではwchar_tで管理することに。結局内部コードをもつ文字列クラスはwin専用クラスに……。将来的にはこの文字列クラスの中身を書き換えるだけでいろいろと変更に対応出来る感じに組むしかないな……。
■サロゲートペア問題は局所的問題として解決
内部コードは現状、文字変換の標準ライブラリが使えるのでwchar_t(=utf16)がベターだとおもわれるのだけどもutf16にはサロゲートペアというお荷物仕様が。
問題になるのは文字列分割の際に、その境界にサロゲートペアの文字が来たときの場合なので、ゲームPGでは文字を1文字づつぺろぺろだすメッセージウィンドウとかぐらいでしょうか。ゲームスクリプトの解析なんかはスクリプト用のコマンドは半角限定にすればサロゲートペアの影響は受けないですし。
サロゲートペア対応はそれに特化したメッセージウィンドウクラスの中でのみ対処して、文字列クラスのなかではサロゲートペアがらみの処理は一切省くかんじで良いと思われる。(文字コードの相互変換部分では必要だけど)
サロゲートペアの表示自体はDXライブラリとかのお仕事なので気にしない。
そんなかんじですすめてみるかーと。
で、各種文字コード変換がひつようになるのだけども。
標準ライブラリの文字コード変換のstd::codecvtはどうも実装がかなりおそまつでセキュアでもないってことでc++17で非推奨……。代替用意してから消えて欲しいものですw
てことでutf8←→utf16はwinapiでもあるのだけどもそれ以外のutf16←→utf32とutf8←→utf32がwinapiでは用意されていない。
ということでその辺自前で実装することに。
そこでutf8←→utf16の変換、自前実装とwinapiとでコスト的にどのくらい差があったりするものなのか……と気になったので計測してみたり。
utf8→utf16変換
結果の左は変換文字数を400文字 右は20文字
それぞれ10000回変換した結果。
MultiByteToWideChar(winapi)
debug = 280ms 210ms
relese = 50ms 14ms
自前変換utf8 → utf16
debug = 2370ms 300ms
relese = 54ms 12ms
ふむう。さすがwinapi。
独自最適化されてるせいか、デバッグビルドでもそこそこ速い。
一方自前実装の方はデバッグビルドだとクッソ遅いw
結果受け取りのバッファにstd::unique_ptr使ってるところで、リリースビルドだと生ポインタとほぼ同じコストになるけどデバッグビルドだとクッソ遅い。ってところで足を引っ張ってる物とおもわれる。
しかし何度か実行した平均値をとってきてるのだけども、少ない文字数の場合リリースビルドだと、微妙に自前実装のが速かったりする。文字数多い場合だとちょっと負けてるけど。
その辺、MultiByteToWideCharではやってる安全チェックのいくつかをすっ飛ばしてる部分の差が出てるのかなーとか。
そんな感じでutf8←→utf16変換に関しては、vc++上ではwinapiをつかって、win以外の方では自前実装のものを使う感じの運用にすることに。
あとは10000回変換してこの速度なら、毎度しょっちゅうコード全域にわたって文字コード変換してもたいしたコストじゃないんだなーやっぱり。とかおもたり。
QTのソースコードと互換性ほしいので、文字列リテラルはu8""で統一したいのだけども、毎度変換コストかかるのはどうなのかなぁとおもてたので。
QTに関してはGUIアプリ作成用なので変換コストとか気にすることはないのだけども、ゲームPGではそうもいなかかったりするし。
しかし何となく適当に多めの文字列ってところで適当な文字列をその辺からコピペしたのがだいたい400文字ぐらいだったのだけども。
400文字て普通の原稿用紙一枚分なんだよなと。
で10000回変換てことは原稿用紙10000ページ分てと、一般的な文庫本だと一冊400字詰め原稿用紙換算だと500ページぐらいらしいので小説本20冊分ぐらいか。
適当にぐぐってみたところ、一般的なビジュアルノベルの文章量てのは50万文字ぐらいとからしい(しっかりとボリュームある感じの場合? すくないのは20万文字とかいうデータもあったのでよくわからん)50万/400で原稿用紙1250ページ分とかんがえるとその約10倍の量のテキストを読み込んで(ファイル読み込みにかかる時間はこの際無視して)一括でutf8からutf16に変換してもリリースビルドなら50msしかかからないのかーと思うと、ゲームで使うテキストファイルも変換コスト避けるために内部コードと同じ文字コードに……なんてのもセコセコバッタの無駄な努力なのだなぁとかおもてしまったりw
無視出来るレベルのコストのために、無駄な面倒抱えるべきではないですね……。
そんな感じで、まだまだほんとにこれでいいのか? と模索しながらの文字コード周り。
いっそもうwin専用としてガッツり書いた方が楽なんだけどなーとか一瞬楽な方に流れそうになりながらガリガリと。
Sun
Mon
Tue
Wed
Thu
Fri
Sat
01
02
03
■
■
台風よくくるな
04
■
■
遅々として
05
06
07
08
[体育の日]
09
10
11
12
13
14
15
■
■
c++の闇は深い
16
17
18
19
20
■
■
数式こわい
21
22
23
■
■
車輪~
24
25
26
27
28
29
30
31
total:2077059 t:67 y:504
■記事タイトル■
■年度別リスト■
2024年
2024年12月(0)2024年11月(0)
2024年10月(1)
2024年09月(2)
2024年08月(1)
2024年07月(1)
2024年06月(5)
2024年05月(2)
2024年04月(1)
2024年03月(6)
2024年02月(4)
2024年01月(3)
2023年
2023年12月(3)2023年11月(1)
2023年10月(2)
2023年09月(3)
2023年08月(3)
2023年07月(3)
2023年06月(7)
2023年05月(8)
2023年04月(2)
2023年03月(1)
2023年02月(2)
2023年01月(3)
2022年
2022年12月(4)2022年11月(3)
2022年10月(1)
2022年09月(3)
2022年08月(3)
2022年07月(2)
2022年06月(1)
2022年05月(3)
2022年04月(2)
2022年03月(2)
2022年02月(1)
2022年01月(6)
2021年
2021年12月(8)2021年11月(3)
2021年10月(4)
2021年09月(6)
2021年08月(2)
2021年07月(1)
2021年06月(3)
2021年05月(2)
2021年04月(2)
2021年03月(3)
2021年02月(1)
2021年01月(4)
2020年
2020年12月(3)2020年11月(7)
2020年10月(2)
2020年09月(3)
2020年08月(1)
2020年07月(3)
2020年06月(7)
2020年05月(5)
2020年04月(8)
2020年03月(4)
2020年02月(2)
2020年01月(4)
2019年
2019年12月(1)2019年11月(1)
2019年10月(2)
2019年09月(1)
2019年08月(3)
2019年07月(2)
2019年06月(2)
2019年05月(2)
2019年04月(4)
2019年03月(1)
2019年02月(7)
2019年01月(1)
2018年
2018年12月(1)2018年11月(1)
2018年10月(5)
2018年09月(1)
2018年08月(5)
2018年07月(1)
2018年06月(1)
2018年05月(1)
2018年04月(2)
2018年03月(2)
2018年02月(1)
2018年01月(1)
2017年
2017年12月(2)2017年11月(1)
2017年10月(2)
2017年09月(5)
2017年08月(8)
2017年07月(2)
2017年06月(1)
2017年05月(1)
2017年04月(3)
2017年03月(5)
2017年02月(7)
2017年01月(8)
2016年
2016年12月(7)2016年11月(2)
2016年10月(3)
2016年09月(7)
2016年08月(8)
2016年07月(10)
2016年06月(17)
2016年05月(6)
2016年04月(8)
2016年03月(10)
2016年02月(5)
2016年01月(10)
2015年
2015年12月(7)2015年11月(7)
2015年10月(13)
2015年09月(7)
2015年08月(7)
2015年07月(5)
2015年06月(4)
2015年05月(5)
2015年04月(2)
2015年03月(4)
2015年02月(1)
2015年01月(7)
2014年
2014年12月(12)2014年11月(8)
2014年10月(4)
2014年09月(6)
2014年08月(7)
2014年07月(4)
2014年06月(2)
2014年05月(5)
2014年04月(4)
2014年03月(8)
2014年02月(4)
2014年01月(8)
2013年
2013年12月(15)2013年11月(8)
2013年10月(3)
2013年09月(3)
2013年08月(8)
2013年07月(0)
2013年06月(0)
2013年05月(0)
2013年04月(0)
2013年03月(0)
2013年02月(0)
2013年01月(0)
■レス履歴■
■ファイル抽出■
■ワード検索■
堕天使の煉獄
https://rengoku.sakura.ne.jp
管理人
織田霧さくら(oda-x)
E-mail (■を@に)
oda-x■rengoku.sakura.ne.jp