Boost プログラミング

boost は STL のように非常に汎用性をもったC++ライブラリ群なので ある。言語の拡張とも言えるほど汎用性があるし、ほとんどが ヘッダファイルだけでできた template なので、コンパイルさえ 通ればどのプラットフォーム上でも使える。すばらしい。

Contents

Boost.Jam

システムに付属の g++ と違うバージョンのものを使いたいなどの 要望が多々あると思うのでメモ。

g++のインストール

これは、:

% ./configure --prefix=/usr/local/gcc34

とかやればよい。

bjam のインストール

次に boost をコンパイルするためのツール:

% cd <boost-dir>/tools/build/jam_src
% ./build.sh

UNIX系に根ざした make と違い、bjam は Windows も OK な とても汎用的なツールなのだけれど、boost のコンパイル以外で 使ったことはないし、見たこともないので、/usr/bin とかには インストールしない。:

% cp bin.linux/bjam ../../..
% cd ../../..

boost のインストール

システムの g++ は使わないので、bjam にいっぱいオプションを指定しないと いけない。詳しくは、<boost-dir>/more/getting_started.html を参照。:

% ./bjam -sTOOLS=gcc -sGCC_ROOT_DIRECTORY=/usr/local/gcc34 -sGXX=g++-3.4 -sGCC=gcc-3.4 -sHAVE_ICU=0 --prefix=/usr/local/gcc34

てな具合で、かなりオプションを指定しないといけない。できたら 上記のコマンド + "install" で OK。

Boost.TypeTraits

is_const とポインタ

boost::is_const<T> で、T の型が const か const でないかを判断してくれる。 では問題、このうち is_const で const と判断されるのはどれか?

  1. const char*
  2. char const*
  3. char* const

正解は3。ちょっと予想と違う動きなので注意が必要。 この調子で remove_const も注意して使用すること。

Boost.MPL

シーケンスの型

boost::mpl::list や boost::mpl::vector などは、型を追加したり、 削除したりできて非常に便利なのだが、あんまり厳密でない。 例えば [ int, char ] というリストがあって、push_front で float を加え、[ float, int, char ] という型をつくる。 で、その後、pop_front で [ int, char ] という型に戻しても、 それは、もとの型と同じではない。ただし同じリストである。 boost::is_same では違うけれど、boost::mpl::equal では同じなのだ。:

#include <iostream>

#include <boost/type_traits.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/push_front.hpp>
#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/equal.hpp>

int
main(
        int             argc,
        char**          argv
        )
{
        using namespace boost::mpl;
        using std::cout;
        using std::endl;

        typedef list<int, char>                 Seq1; // <int,char>
        typedef push_front<Seq1, float>::type   Seq2; // <float,int,char>
        typedef pop_front<Seq2>::type           Seq3; // <int,char>

        // check the equality as C++ Type
        cout << (boost::is_same<Seq1, Seq3>::value ? "yes" : "no") << endl;

        // check the equality as MPL Sequence
        cout << (equal<Seq1, Seq3>::type::value ? "yes" : "no") << endl;
}

上の結果は、:

no
yes

になる。

Boost.Lambda

Boost::Bind との併用で気をつける _1 や _2

boost::lambda には、_1 とか bind とかが定義されているが、これは Boost.Bind ライブラリの _1 とか bind とかとは違う名前空間で 定義されているものなのでとっても気を付ける。具体的には、

Boost.Bind で ::_1 が定義され、Boost.Lambda で ::boost::lambda::_1 が 定義されているので、"using namespace boost::lambda;" とかいうことを していて、しかも"#incude <boost/bind.hpp> とかしていると _1 は 曖昧、とか怒られてコンパイルが通らない。なので明示的に ::boost::lambda::_1 とかした方が無難かと思われる。

以下は一応 GCC-3.2.2 (RedHat9) で確認済みのコード。

なんだか、簡単になっているのか、複雑になっているのかよくわからない けど、「偶数だったら出力」とか「奇数だったら出力」とかいう 関数オブジェクトを作らずにすんだ。:

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <functional>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/mem_fn.hpp>
#include <boost/bind.hpp>

using namespace std;
// using namespace boost::lambda;

class A
{
public:
        A(int n): m_val(n) {}
        bool isEven() { return m_val % 2 == 0; }
        void print() { cout << m_val << endl; }

private:
        int m_val;
};

int
main()
{
        vector<A *>     v;

        v.push_back(new A(5));
        v.push_back(new A(4));
        v.push_back(new A(3));
        v.push_back(new A(2));
        v.push_back(new A(1));

        cout << "Printing even numbers ..." << endl;

        for_each(v.begin(), v.end(),
        ::boost::lambda::if_then(
                ::boost::lambda::bind(&A::isEven, ::boost::lambda::_1),
                ::boost::lambda::bind(&A::print, ::boost::lambda::_1)
                ));

        cout << "Printing odd numbers ..." << endl;

        for_each(v.begin(), v.end(),
        ::boost::lambda::if_then(
                ! ::boost::lambda::bind(&A::isEven, ::boost::lambda::_1),
                ::boost::lambda::bind(&A::print, ::boost::lambda::_1)
                ));
}

Boost.Test

Boost.Test は単体テストをするのに便利なツールを集めたモジュール。 プログラムにBoostを使っているなら、一緒に使えるので便利。

このモジュールを使って単体テストを行って始めて良さがわかった。

  1. テストケースを考えること自体が、バグのあぶり出しになっている。
  2. テストケースを考えることで、モジュールの設計を見直し、より 汎用性のあるインタフェースに改善する機会を得る。
  3. テストケースが、モジュールの使用法に関する良い例となる。
  4. テストがすべて通ると「嬉しい」、これが一番重要。

基本的な使い方

int main(int argc, char** argv) のかわりに、こんな関数を定義する。:

using namespace boost::unit_test;
using namespace boost::unit_test_framework;

test_suite*
init_unit_test_suite(int argc, char** argv)
{
        test_suite* test = BOOST_TEST_SUITE("適当な名前");

        test->add(BOOST_TEST_CASE(&test_function_1));
        test->add(BOOST_TEST_CASE(&test_function_2));
        test->add(BOOST_TEST_CASE(&test_function_3));

        return test;
}

で、実際にテストを行う関数(test_function_1 ..., test_function_x)は void test_function (void) な関数。

テスト結果の出力

出力先の設定 unit_test_log.set_stream( std::ostream )
出力レベルの設定 unit_test_log.set_threshold_level( <log_level> )