シングルファイル C/C++ ライブラリの概要や利点についてはこちら。
https://qiita.com/syoyo/items/e9d4aff56f691f5b783b
私の知る限りシングルファイルC/C++ライブラリの実装方法には以下の2種類がある(他にもあったら教えてほしい)。
なおシングルファイルC/C++ライブラリを使うメリットはどこでも簡単に動かせることだと思うので、この記事では移植性を損なうコンパイラの拡張機能を使うことは考えない。
#ifdef
で宣言と実装を切り替える方法
1ファイルの中に宣言と実装を分けていれておき#ifdef FOO_IMPLEMENTATION
により切り替える方法。
宣言されている関数を使う時は#include
して宣言を参照する。
実装したいファイルでは#define FOO_IMPLEMENTATION
を#include
の前に書き、実装を読み込む。
実装するのはプログラム内で唯一のファイルである必要がある。
この方法の代表的なライブラリとしてはstbやtinyobjloaderがある。
この方法の問題はどこで実装するかをプログラマが意識してコントロールしなければならない点だ。
複数個所で実装すると当然リンク時に重複シンボルエラーになる。
この問題はシングルファイルC/C++ライブラリを使ったライブラリの移植性の低下につながる。
例えばライブラリAでstbを使っていた(実装していた)とする。
そのことを知らずに別途stbを実装しているアプリケーションBでライブラリAをリンクすると重複シンボルエラーになる。
template
(or inline
) で書く方法
こちらはC++限定になるがtemplate
で書く方法。
この方法は#ifdef
を使う方法よりも流行っていないようだが、例としてはNanoRTが挙げられる。
私自身もこの方法でNanoPMというPatchMatchのシングルファイルC/C++ライブラリを作ったことがある(名前はNanoRTをリスペクトしてつけた)。
#include
するだけで使えるので#ifdef
を使う方法よりも扱いやすいと私は思う。
template
の問題としてコンパイル時間と実行ファイルサイズが増加する可能性がある。
また、プログラム全体でバージョンが統一されていないと実行時の挙動が予測できない。
あるtemplate
ライブラリのv0.1
を使ったライブラリAと、v0.2
を使ったライブラリBをリンクすると、同じ名前だが実装が異なるためどちらかのシンボルで上書きされ実行時の挙動が予測できない。
コンパイルとリンクが通ってしまい、さらに実行時も動いたり動かなかったりするのが厄介だ。
シングルファイルではないがEigenでこの問題によく悩まされる。
比較
#ifdef 方式 |
template 方式 |
|
---|---|---|
言語 | C/C++ | C++のみ |
コンパイル時間 | 普通 | 長め |
実行ファイルサイズ | 普通 | 大きめ |
実装ファイルをプログラマが指定 | する | しない |
重複シンボルエラー | 起きやすい | 起きにくい |
実行時エラー | 起きにくい | 起きやすい |
この表だけを見ると一長一短だが、#ifdef
方式の実装ファイルをプログラマが指定するというのがだるすぎるので私はtemplate
方式推しだ。
シングルファイルC/C++ライブラリを自作ライブラリに組み込むベストプラクティス
を知りたい。
前述したように単純に自作ライブラリに組み込むとリンクした際に#ifdef
方式では重複シンボルエラー、template
方式では実行時エラーになる可能性がある。
そこでgit submodule
などで元のコードを参照するのではなく、コードを改変して自作ライブラリ名前空間に入れたり関数名に自作prefixをつけたりするのが安全だと思う。
が、改変が面倒だし元のコードのバージョンを保持しておくのも面倒なのでもっといい方法を探している。