Boostをサポート環境外で動かす

このエントリは、C++ Advent Calendar 2012の参加記事です。


Boost C++ Librariesは多くの環境をサポートしていますが、Boostでも手の届かない環境があります。それは、公開されていない開発環境です。たとえば、ゲームコンソールの開発環境は公開されていないので、ワークアラウンドを自分で書くことはできても、それを公開することはできません。
そういった環境で「Boost使えない!」と言って終わってしまうケースをたまに見ますが、Boostは多くの環境をサポートするために機能ごとのワークアラウンドを提供しているので、自分で設定を書けばサポート環境外でもBoostが使えます。
この記事は、クローズドなサポート外環境への設定を書く方法と、サポート環境とサポート外環境を含めたマルチプラットフォーム化のノウハウを紹介します。

  • 環境を知り、環境を入力する

Boostでは、環境差異を吸収するためのBoost.Configというワークアラウンドライブラリが提供されています。このライブラリは、「この環境ではC++のxx機能を持っている/持っていない」を判定するためのマクロを、各環境向けに定義します。
2012年現在のサポート外環境において、Boostを使うとコンパイルエラーになる最大の要因は「C++11に対応していない」でしょう。Boost.Configのデフォルトでは、「この環境ではC++11を含む全ての機能が提供されている」という状態になっているので、C++11に対応していないサポート外環境では、Boostの多くの機能はコンパイルエラーになるでしょう。

#include <boost/static_assert.hpp>

int main()
{
    BOOST_STATIC_ASSERT(1 < 2); // エラー!static_assertキーワードがない!
}

Boost.Configにある「C++11の機能を持っていない」ことを示すマクロをdefineすれば、その環境がC++03と見なされて動作します。以下がその例です:

// my_boost_config.hpp
#ifndef MY_BOOST_CONFIG_INCLUDE
#define MY_BOOST_CONFIG_INCLUDE

#if defined(MY_ENVIRONMENT) // 自分の環境であることを判定する

#define BOOST_NO_CXX11_STATIC_ASSERT      // static_assertを持っていない
#define BOOST_NO_CXX11_RVALUE_REFERENCES  // 右辺値参照を持っていない
#define BOOST_NO_CXX11_VARIADIC_TEMPLATES // 可変引数テンプレートを持っていない
#define BOOST_NO_CXX11_CONSTEXPR          // constexprを持っていない
#define BOOST_NO_CXX11_NOEXCEPT           // noexceptを持っていない

#endif

#endif // MY_BOOST_CONFIG_INCLUDE

このように設定したヘッダファイルは、Boostのヘッダをインクルードする前に必ずインクルードします。
Boost.Configの設定マクロは、ここでは紹介しきれないので、ドキュメントを参照してください。


C++03対応の欠陥を設定するマクロ
オプショナルな機能の保持状態を設定するマクロ
C++11のサポートしていない機能を設定するマクロ


自分の環境がどの機能を持っていて、どの機能を持っていないか、ここで記述することはできませんので、自分の環境のドキュメントを自分で確認するか、コンパイルエラーになった機能に対してひとつずつマクロを設定していくかしてください。私はだいたい後者でやってます。

前述した設定マクロは、サポート環境外の自分の環境を入力するだけで、サポート環境と自分のサポート外環境両方で動作するようになります。
しかし、Boostのライブラリは、Boost.Configの設定だけでは動作するようにならないものもあります。
たとえばBoost.Threadです。このライブラリはWindowsスレッドとpthreadを切り替えて使用するライブラリですが、これら以外の環境でBoost.Threadを使おうとすると、以下のようなコンパイルエラーになってしまいます。

Boost threads unavailable on this platform
「Boost.Threadはこのプラットフォームでは使用できません」

これは、Boost.Threadの実装が以下のようになっているからです:

// boost/thread/thread.hpp

#include <boost/thread/detail/platform.hpp>

#if defined(BOOST_THREAD_PLATFORM_WIN32) // Windowsの場合
    #include <boost/thread/win32/thread_data.hpp>
#elif defined(BOOST_THREAD_PLATFORM_PTHREAD) // pthreadの場合
    #include <boost/thread/pthread/thread_data.hpp>
#else // それ以外はエラー
    #error "Boost threads unavailable on this platform"
#endif

これは、ワークアラウンドが書きにくいという意味ではBoost側の欠陥ですが、ワークアラウンドを公開できないプラットフォームがオープンソースに文句を言うのは筋違いということで・・・。
こういった、各環境によって提供されているライブラリをラップすることでマルチプラットフォーム化したライブラリの場合はどうすればいいでしょうか。自分の環境向けにはもちろん使いやすいライブラリはあるけど、Windowsやpthreadのような環境でも動かしたい、という場合です。Boostに頼ることなく自分でマルチプラットフォーム化するというのは単に無駄な労力でしょう。多くの人によってパッチが投稿されテストされてきた実績はそのままいただいてしまいたいです。
いろいろやり方はあると思いますが、ここで私は以下の方針を提案します:


「自分の環境向けにBoostライクなインタフェースのライブラリを書き、Boostがサポートしている環境ではBoostを使う」


自分でBoostを書けというのか!と引いてしまうかもしれませんが、各環境のライブラリをラップしただけのライブラリは、所詮ただのラッパーなので難しくありません。Boost.Threadの場合は、以下のように書けばいいでしょう。

// my_thread.hpp
#ifndef MY_THREAD_INCLUDE
#define MY_THREAD_INCLUDE

#if defined(MY_ENVIRONMENT) // 自分の環境向けにboost::threadを作る
    namespace boost {
        class thread {
            my_handle handle_;
        public:
            template <class F>
            explicit thread(F f)
            {
                start_thread(handle_, f);
            }

            void join()
            {
                join_thread(handle_);
            }

            ...
        };
    }
#else
    #include <boost/thread.hpp> // 自分の環境以外はBoostをそのまま使う
#endif

#endif // MY_THREAD_INCLUDE

関数オブジェクトをスレッドで動かす方法などは、私の以下のエントリを参考にしてください。


Boost.Threadの中でやってること - Windows編


このような方法をとることで、サポート環境とサポート外環境、両方でBoostが使えるようになります。
Boostのインタフェースを全て実装するのが難しい場合は、サポート環境でBoostをそのまま使うのではなく、ミニマムなサブセットとしてBoostをラップすればいいでしょう。

  • おわりに

この記事では、Boostをサポート環境外で動かすためのノウハウを紹介しました。
「オープンな環境なのにBoostがサポートしてくれない!」ならパッチを送れば済む話ですが、クローズドな環境はそうはいきません。そういった環境で開発することになった場合、「この環境ではBoost使えない!」で終わってしまうのではなく、もう少し粘ってみましょう。
少し調べるだけで、サポート環境外でもBoostが使えるようになります。Boost.Configと、各ライブラリが提供するワークアラウンドを知ってください。Boostは多くの人によって絶えず改善されている人類の叡智です。諦めずに使いましょう!


C++ Advent Calendar、2日目のid:osyo-mangaさんに続きます。