*C++ で書くと、何を得て何を失うのか。
*C++ は、別の言語に乗り換えるものではありません。 いまお使いの C++ の上に載せる選択肢です。メモリ管理させる部分だけを、*C++ で書きます。
全て有効な姿では、メモリ安全の問題が消えます。
解放済みの領域を使う、二度解放する、返し忘れる。
どれも寿命をプログラマーが管理しているために起きます。
仕組みが寿命を管理すれば、プログラマーの手違いでは起きません。
スレッドの競合も、排他の印(= ロック)で囲んでいない所を見つけたら、実行を停止します。
書き方の制約も、安全に効きます。
スコープを抜けた実体を指し続ける、切り捨て、ポインタ演算、基底のずれ、new と delete の対の不一致。
*C++ で書いた部分では、どれも書けない形なので起きません。
寿命を任せた配列は、実行時の長さを持ちます。 生の配列(T[])と違い、*C++ の配列は自分の長さを持つので、Java や C# の拡張 for(= foreach)と同じ形で、どの配列でも回せます。添字は境界で止まります。 入れ物も同じです。文字列やコレクションのような入れ物を *C++ で書けば、その中身まで寿命の管理とコンパクションの対象になります。
オブジェクトの寿命を、考えずに書けるようになります。
Java や C# を書くときと同じ要領です。
new と delete の対応、どの型のポインタを選ぶか、循環参照をどこで切るか。
これらを決める必要が、なくなります。
開発が速くなります。 寿命を決める作業が無くなった分、書く時間が短くなります。
その部分のデバッグが要らなくなります。 全て有効な姿では、メモリの不具合は、書けないか、起きないか、その場で理由を出して止まります。離れた場所に出た症状から原因を探す作業が、無くなります。
実行速度が落ちます。全て自分で書いた C++ よりは、安全性と効率性が高くなった分だけ処理が掛かります。 ただし、Java や C# の実行環境よりは速く動き(= 実際の製品で確かめた範囲です)、さらに、次の方法で速さを回復できます。
回収の進み具合を設定できます。通常は毎周期(= ゲームなら毎フレーム)少しずつ進め、画面の切り替わりや読み込み中、通信の待ちなど、まとめて止めてよい瞬間に呼ぶこともできます。詳しくは、YamatoMM の 取り扱い方法 のページをご覧ください。
安全の検査を 1 つずつ外せます。null、境界、ゼロクリア、型、競合の 5 つは、テストモードでは全て有効、リリースモードでは要らない検査を外して速くします。詳しくは、安全性 のページをご覧ください。
任せ方を段階で選べます。速さが要る所だけ外し、それ以外は任せられます。詳しくは、効率性 のページをご覧ください。
書き方が変わります。
任せる型の書き方は、Java や C# のオブジェクトの形になります。任せない部分は、いまの C++ のままです。
*C++ では、そのままの C++ を、一字も直さずに動かせるものではありませんが、変換ツールでほとんどの修正を行う形です。
new と delete をはじめ、必要な書き換えをまとめて行います。
寿命を任せた型には、任せた分の制約が付き、次の 4 つが使えなくなります。
実体を直接置く。
T t; のように、スタックや他の型の中に実体を置く書き方です。
実体はリストに載る所(= ヒープ)にだけ作られ、参照で指します。
生ポインタで持つ。
T* や T&、this を保存して後で使う書き方です。
コンパクションで実体が動くので、参照で持ちます。関数の引数も参照で渡します。
データを持つ基底を、2 つ以上継承する。 任せる型は単一継承です。複数の型として扱いたいときは、データを持たない型(= Java や C# の interface に当たるもの)を多重に継承します。
自分で delete する。
delete や free()、独自の operator new / operator 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)実行環境が回収する | 仕組みの側 | 要りません | 遅い | 壊れません検出はしません。 | 呼ばない時にも止まります |
※ 速さは、お使いになる環境によって変わります。