株式会社空想科学テクノロジー

トレードオフ

*C++ で書くと、何を得て何を失うのか。

長所と短所

*C++ は、別の言語に乗り換えるものではありません。 いまお使いの C++ の上に載せる選択肢です。メモリ管理させる部分だけを、*C++ で書きます。

長所

全て有効な姿では、メモリ安全の問題が消えます。 解放済みの領域を使う、二度解放する、返し忘れる。 どれも寿命をプログラマーが管理しているために起きます。 仕組みが寿命を管理すれば、プログラマーの手違いでは起きません。 スレッドの競合も、排他の印(= ロック)で囲んでいない所を見つけたら、実行を停止します。 書き方の制約も、安全に効きます。 スコープを抜けた実体を指し続ける、切り捨て、ポインタ演算、基底のずれ、newdelete の対の不一致。 *C++ で書いた部分では、どれも書けない形なので起きません。

寿命を任せた配列は、実行時の長さを持ちます。 生の配列(T[])と違い、*C++ の配列は自分の長さを持つので、Java や C# の拡張 for(= foreach)と同じ形で、どの配列でも回せます。添字は境界で止まります。 入れ物も同じです。文字列やコレクションのような入れ物を *C++ で書けば、その中身まで寿命の管理とコンパクションの対象になります。

オブジェクトの寿命を、考えずに書けるようになります。 Java や C# を書くときと同じ要領です。 newdelete の対応、どの型のポインタを選ぶか、循環参照をどこで切るか。 これらを決める必要が、なくなります。

開発が速くなります。 寿命を決める作業が無くなった分、書く時間が短くなります。

その部分のデバッグが要らなくなります。 全て有効な姿では、メモリの不具合は、書けないか、起きないか、その場で理由を出して止まります。離れた場所に出た症状から原因を探す作業が、無くなります。

短所

実行速度が落ちます。全て自分で書いた C++ よりは、安全性と効率性が高くなった分だけ処理が掛かります。 ただし、Java や C# の実行環境よりは速く動き(= 実際の製品で確かめた範囲です)、さらに、次の方法で速さを回復できます。

回収の進み具合を設定できます。通常は毎周期(= ゲームなら毎フレーム)少しずつ進め、画面の切り替わりや読み込み中、通信の待ちなど、まとめて止めてよい瞬間に呼ぶこともできます。詳しくは、YamatoMM の 取り扱い方法 のページをご覧ください。

安全の検査を 1 つずつ外せます。null、境界、ゼロクリア、型、競合の 5 つは、テストモードでは全て有効、リリースモードでは要らない検査を外して速くします。詳しくは、安全性 のページをご覧ください。

任せ方を段階で選べます。速さが要る所だけ外し、それ以外は任せられます。詳しくは、効率性 のページをご覧ください。

書き方が変わります。 任せる型の書き方は、Java や C# のオブジェクトの形になります。任せない部分は、いまの C++ のままです。 *C++ では、そのままの C++ を、一字も直さずに動かせるものではありませんが、変換ツールでほとんどの修正を行う形です。 newdelete をはじめ、必要な書き換えをまとめて行います。 寿命を任せた型には、任せた分の制約が付き、次の 4 つが使えなくなります。

実体を直接置く。 T t; のように、スタックや他の型の中に実体を置く書き方です。 実体はリストに載る所(= ヒープ)にだけ作られ、参照で指します。

生ポインタで持つ。 T*T&this を保存して後で使う書き方です。 コンパクションで実体が動くので、参照で持ちます。関数の引数も参照で渡します。

データを持つ基底を、2 つ以上継承する。 任せる型は単一継承です。複数の型として扱いたいときは、データを持たない型(= Java や C# の interface に当たるもの)を多重に継承します。

自分で delete する。 deletefree()、独自の operator newoperator delete です。 寿命は仕組みが決めます。ファイルや GPU の資源の後始末は、デストラクタに書きます。デストラクタは、参照が無くなった瞬間に走ります。互いに参照し合っていたものは、回収のときに走ります。

生ポインタが必要な所(= OS / GPU / ファイル / 他の C ライブラリ)は コンパクションで動かせませんので、そこを指定して除きます。 C++ の標準ライブラリの入れ物(= std::vector など)も、この側です。任せる型の中に置ける任せない型は、そのまま写して動かせる物に限ります(= 自分の中を指す物は置けません)。 この 4 つのどれかが要る型は、任せない側(= いまの C++ のまま)に置きます。 1 つのプログラムの中に、任せる型と任せない型の両方を置けます。

どこに位置するか

方法 寿命の決め方 その部分のデバッグ 速さ 競合をどう扱うか いつ止まるか
いまの C++ / Rust確保と解放を自分で書く / 所有権を型で書く プログラマー自身 C++:要りますRust:コンパイル時に済みます 速い C++:何もしませんRust:コンパイル時に排除します 回収では止まりません
*C++ 仕組みの側 要りません この間いまの C++ より遅く、Java や C# より速く動きます。 見つけたら、実行を停止します プログラマーが決めます毎周期の上限を決め、まとめて止めてよい瞬間には呼んで実行できます。
Java(JVM)・C#(.NET)実行環境が回収する 仕組みの側 要りません 遅い 壊れません検出はしません。 呼ばない時にも止まります

※ 速さは、お使いになる環境によって変わります。