boostのobject_poolをスマートポインタで利用する

boostにはメモリプールをやってくれる親切なobject_poolと言うライブラリがある.この使い方は至って簡単で,テンプレートの引数にクラスを渡してプール用のオブジェクトを作成するだけとなる.指定したクラスの生成を行いたい場合(new classに相当)はconstruct関数を,削除したい場合(delete classに相当)はdestroy関数を呼び出すだけである.サンプルプログラムは以下のとおりとなる.

#include <iostream>
#include <string>

#include <boost/pool/object_pool.hpp>

class obj {
public:
        std::string str;

        obj() { }
        virtual ~obj()
        {
                std::cout << "delete: " << str << std::endl;
        }
};

int
main(int argc, char *argv[])
{
        boost::object_pool<obj> obj_pool;
        obj *p_obj1, *p_obj2, *p_obj3;

        p_obj1 = obj_pool.construct();
        p_obj1->str = "obj 1";

        p_obj2 = obj_pool.construct();
        p_obj2->str = "obj 2";

        p_obj3 = obj_pool.construct();
        p_obj3->str = "obj 3";

        obj_pool.destroy(p_obj2);

        return 0;
}

obj_pool.construct() が new obj に相当する部分となっている.

object_poolは便利だが生でポインタが返ってくる.もはや,C++で生ポインタを扱うのは*古い*書き方であり,最近ではshared_ptrなどのスマートポインタを利用するのが常識である(俺の中で).

通常,boostのスマートポインタであるshared_ptrはオブジェクトを削除するためにdeleteを使う.newで作成したオブジェクトであれば問題ないのだが,object_poolで作成したオブジェクトに対してはdeleteの代わりにdestroy関数を使わなければならない.実は,shared_ptrのコンストラクタの第二引数には独自の削除関数を指定することが出来るので,これを指定してやれば良い.実際のコードは以下のようになる.

#include <iostream>
#include <string>

#include <boost/pool/object_pool.hpp>
#include <boost/shared_ptr.hpp>

class obj {
public:
        std::string str;

        obj() { }
        virtual ~obj()
        {
                std::cout << "delete: " << str << std::endl;
        }
};

typedef boost::shared_ptr<obj> obj_ptr;

boost::object_pool<obj> obj_pool;

void
obj_destroy(obj *ptr)
{
        obj_pool.destroy(ptr);
}

int
main(int argc, char *argv[])
{
        obj_ptr p_obj1, p_obj2, p_obj3;

        p_obj1 = obj_ptr(obj_pool.construct(), obj_destroy);
        p_obj1->str = "obj 1";

        p_obj2 = obj_ptr(obj_pool.construct(), obj_destroy);
        p_obj2->str = "obj 2";

        p_obj3 = obj_ptr(obj_pool.construct(), obj_destroy);
        p_obj3->str = "obj 3";

        p_obj2.reset();

        return 0;
}

このプログラムでは,コンストラクタの第二引数に独自の削除関数であるobj_destroyを指定している.こうすることで,shared_ptrがオブジェクトの削除を行う際には,この関数が呼ばれるようになる.この関数は,コピーコンストラクタを持つ関数オブジェクトにしても大丈夫である.ただし,コンストラクタの中で例外を投げてはいけない.

これで一応,object_poolから得たポインタもスマートポインタで扱いたいと言う要求は達成できたように思える.object_poolを使う理由は頻繁にnewやdeleteを行いたくないからであった.しかしながら,shared_ptrでは参照カウンタのために内部でメモリ確保を行っており,これでは本末転倒である.そこで登場するのがintrusive_ptrとなる.

intrusive_ptrは,対象となるオブジェクト自身で参照カウンタを保持しなければならないが,参照カウンタのためのメモリ確保はしなくて良い.intrusive_ptrを利用するには,参照カウンタを増やす関数のintrusive_ptr_add_refと減らす関数のintrusive_ptr_releaseを定義しなければならない.intrusive_ptrを使ったコードは以下のようになる.

#include <iostream>
#include <string>

#include <boost/pool/object_pool.hpp>
#include <boost/intrusive_ptr.hpp>

class obj {
public:
        std::string str;

        int refc;

        obj() : refc(0) { }
        virtual ~obj()
        {
                std::cout << "delete: " << str << std::endl;
        }
};

typedef boost::intrusive_ptr<obj> obj_ptr;

boost::object_pool<obj> obj_pool;

void
intrusive_ptr_add_ref(obj *p_obj)
{
        p_obj->refc++;
}

void
intrusive_ptr_release(obj *p_obj)
{
        p_obj->refc--;

        if (p_obj->refc <= 0) {
                obj_pool.destroy(p_obj);
        }
}

int
main(int argc, char *argv[])
{
        obj_ptr p_obj1, p_obj2, p_obj3;

        p_obj1 = obj_ptr(obj_pool.construct());
        p_obj1->str = "obj 1";

        p_obj2 = obj_ptr(obj_pool.construct());
        p_obj2->str = "obj 2";

        p_obj3 = obj_ptr(obj_pool.construct());
        p_obj3->str = "obj 3";

        p_obj2.reset();

        return 0;
}

オブジェクト自身がrefcという参照カウンタを持ち,intrusive_ptr_add_refとintrusive_ptr_releaseで参照カウンタの加算と減算を行っている.参照カウンタが0になった時点で,オブジェクトを解放している.intrusive_ptrを使えば,参照カウンタのためのメモリ確保をする必要が無いので,object_poolから得たポインタをスマートポインタで扱いたい場合はintrusive_ptrの方が良いと考えられる.