C++をラップしてC向けにポーティングする時の書き方の考察
どういう状況
C++向けのライブラリをC FFIを持つ言語で使いたいので、C用のインターフェースを用意して他の言語から呼び出せるようにする。
zig-gamedevのzbulletのような状態。
自分の想定ではzigで使いたいライブラリがC++にあるので、それをラップするときのC/C++のコードをどう書くか思案してみる。
ラップするだけなら新たにコードを書く必要が無い(ほぼヘッダだけ書けばいい)ので、あんまり悩まないが今回はまとまった量のC++のコードを書かないといけないので方針を決めたい。
前提条件
そもそもC++のコードを新規で書きたくないというのもある。
何で新規で書きたくないのか。
- 言語仕様が複雑すぎるので保守が大変
- ビルドシステム周りが大分アレなので保守が大変
- OOPが嫌いなのでOOPを指向している言語をわざわざ書きたくない
- new/deleteのメモリ管理(というかOOP)は管理が大変で保守が辛い
特定のライブラリを使いたいという事情が無ければ書きたくない。
(もし新規でポータブルに使えるライブラリを書きたいならCで書く)
言語仕様が複雑すぎる
所謂BetterCな指向で書く。
と言ってもC++自体に問題が多いから「Better Cっつってもな…」という気持ちも。
とにかくCとあまり変わらないコードを意識して書く。
(継承無し、クラス無し、構造体と関数がメイン)
ビルドシステム周り
zigを使ってビルドする。
build.zig
でビルドできるようにしてzigをCコンパイラとして使う。
OOPで書きたくない
書きたくないので採用しない。
ジェネリクス(template)が使えるC言語として書く。
メモリ管理
ハンドル形式で一元管理してnew/deleteは極力使わない。
微妙な所
- STLの使用はどうする?
- 一様初期化 (uniform initialization)どうする?
- movesemanticsは意識する?
- そもそもC++のターゲットはどの水準?(11,14,17,20)
STLの使用
使っても良いかな。
そもそも使いたいライブラリが使ってたりするし。
極端に寿命が短いか極端に長い変数でなら使っても制御しやすいので、その部分での使用は許す。
一様初期化 (uniform initialization)
結論:使う。
仕様
初期化周りの話
使いたい気持ちも有れば、使いたくないなという気持ちもある。
使いたい理由。
- Cの初期化と近い
- 明示的に初期化を示したい
使いたくない理由。
- C++11だとバグる
- 仕様に不満がある
Cと同じ形で初期化させてくれれば何の問題も無いんだがC++のコレ使いにくいんだよな…。
※Cの方の初期化式も気にくわない部分があるので、一緒だとそれはそれで困る
とはいえ初期化として使いやすいし使う。
バグると言ってもauto
との組み合わせなので型を明示すれば問題ない。
movesemanticsは意識する?
特に意識しない。
Cっぽいコード書くのと、これ意識して書くには相当コンパイラの気持ちを知って無いときついので有るがままの最適化に任せよう。
そもそもC++のターゲットはどの水準?
結論:C++11。
constexprとかラムダとかC++っぽい機能使うならC++17あたりをターゲットにしたい。
ただ、ポータブルに動かすことを考えるとC++11くらいが最大公約数な感じはする。
(C++03は流石に無い)
C++11のきつい所。
- contexprの仕様がやたらキツイ
- template周りは窮屈
- 初期化式のバグ
結局Cっぽいコード書くならC++11で充分か。
C++20でようやくCと同じことできるのか…。
ヘッダ(モジュール)
- C向けに公開するのでCのヘッダとして書く
- 名前空間が使えないので接頭辞で何か付けて衝突を回避する
- 複雑な名前空間は避ける(もしくはローカルで完結させる)
- ソースはC++として書くので公開しないものは名前なし名前空間で囲っておく
依存関係
- 極力少なく保ちたい
- 使うにしてもC言語で書かれたライブラリを使う
- monorepo指向で内部に全て保持して完結する
命名規則
特にこだわりないと思ったが結構迷う。
Cのライブラリだと型名(構造体)も小文字のsneak_caseのパターンが多くて、その後に接尾辞を付ける派閥があったりする。
が、今回はzigから呼び出したいのでzigライクな命名にする。
マクロ
極力使わない。
どうしても使わなくちゃ書けない場合以外は極力避ける。
// 避けられない例
#ifdef __cplusplus
extern "C" {
#endif
理想だけ言うなら#include
以外のマクロは書きたくない。