boost::shared_ptr の Deleter

なんかお仕事で Boost が使えそうなふいんき(←なぜか変換(ry になってきたので今のうちに Boost と戯れて慣れておこう。


boost::shared_ptr はコンストラクタの第二引数に Deleter を設定することができる。

#include <malloc.h>
#include <boost/shared_ptr.hpp>

void* my_malloc(unsigned int size)
{
    return ::malloc(size);
}
void my_free(void* p)
{
    if (p != 0) ::free(p);
}

void main()
{
    // スコープを抜けると自動的に my_free が呼ばれる
    boost::shared_ptr<void> p(my_malloc(100), my_free);
}

別にメモリの解放だけじゃなくて fclose を自動化することもできる。

#include <stdio.h>
#include <boost/shared_ptr.hpp>

void main()
{
    boost::shared_ptr<FILE> p(::fopen("hoge.txt", "r"), ::fclose);
}

ただし、↑のプログラムは間違っていて、fclose の引数に NULL を渡すと不定な動作を起こすので、

#include <stdio.h>
#include <boost/shared_ptr.hpp>

void my_fclose(FILE* p)
{
    if (p != 0) ::fclose(p);
}

void main()
{
    boost::shared_ptr<FILE> p(::fopen("hoge.txt", "r"), my_fclose);
}

こうやって自前の関数を作って null チェックをしてやる必要がある。


でも毎回新しく作るたびに「こいつの null チェック用の関数ってもう書いてたっけ?」とか考えて他のソースを見たりとかするのはどうもめんどい。
かといって毎回ローカルに作るというのも手間なので、Boost.Lambda を使ってさくさくっと書いてみる。

#include <stdio.h>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

void main()
{
    using namespace boost;
    using namespace boost::lambda;
    using boost::lambda::_1;

    shared_ptr<FILE> p(::fopen("hoge.txt", "r"),
        function<void (FILE*)>(
            if_(_1 != static_cast<FILE*>(0))[
                bind(::fclose, _1)]));
}

……全然さくさく書けないorz


……気を取り直して!
例えば Windows API の中には、解放のための API の引数が1つではないことがあったりもする。
たとえば VirtualFree なんかは3つの引数を取る。
この場合は Deleter に直接関数を渡すことはできないので、これも自前の関数を作って、

#include <windows.h>
#include <boost/shared_ptr.hpp>

void my_virtual_free(void* p)
{
    ::VirtualFree(p, 0, MEM_RELEASE);
}

void main()
{
    boost::shared_ptr<void>(
        ::VirtualAlloc(NULL, 100, MEM_COMMIT, PAGE_READWRITE),
        my_virtual_free);
}

こうすればいいんだけど、別の場所に書くのはカッコワルイいので Boost.Bind を使って一発で書いてみる。

#include <windows.h>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

void main()
{
    boost::shared_ptr<void>(
        ::VirtualAlloc(NULL, 100, MEM_COMMIT, PAGE_READWRITE),
        boost::bind(::VirtualFree(), _1, 0, MEM_RELEASE));
}

うんうん、これぐらいならすぐに使えそう。んじゃコンパイル


……なんかいっぱいエラーが出たorz

c:\program files\boost\boost_1_34_1\boost\bind.hpp(66) : error C2825: 'F': '::' が後に続くときは、クラスまたは名前空間でなければなりません
c:\program files\boost\boost_1_34_1\boost\bind\bind_template.hpp(15) : コンパイルされたクラスの テンプレート のインスタンス化 'boost::_bi::result_traits' の参照を確認してください
with
[
R=boost::_bi::unspecified,
F=int (__stdcall *)(LPVOID,SIZE_T,DWORD)
]
c:\ねこフォルダ\ねこ\プログラム\dev\cpptest\cpptest\cpptest.cpp(1859) : コンパイルされたクラスの テンプレート のインスタンス化 'boost::_bi::bind_t' の参照を確認してください
with
[
R=boost::_bi::unspecified,
F=BOOL (__stdcall *)(LPVOID,SIZE_T,DWORD),
L=boost::_bi::list3,boost::_bi::value,boost::_bi::value>
]
...

こんな感じのエラーがいっぱい。
なんか調べてみると、boost はデフォルトでは __stdcall に対する bind を行ってくれないようだ。
これは bind.hpp をインクルードする前に BOOST_BIND_ENABLE_STDCALL を定義しておけば、__stdcall に対する bind も対応してくれるらしい。

#include <windows.h>
#include <boost/shared_ptr.hpp>
#define BOOST_BIND_ENABLE_STDCALL
#include <boost/bind.hpp>

void main()
{
    boost::shared_ptr<void>(
        ::VirtualAlloc(NULL, 100, MEM_COMMIT, PAGE_READWRITE),
        boost::bind(::VirtualFree, _1, 0, MEM_RELEASE));
}

これで良しと。




追記:
null チェック付きの削除を行うための関数を書いてみた。

#include <malloc.h>
#include <stdio.h>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>

template<class TObject>
void _null_check_deleter(TObject* p, boost::function<void (TObject*)> f)
{
    if (p != 0) f(p);
}

template<class TObject>
boost::_bi::bind_t<
    void,
    void (*)(TObject*, boost::function<void (TObject*)>),
    boost::_bi::list2<
        boost::arg<1>,
        boost::_bi::value<
            boost::function<void (TObject*)> > > >
null_check_deleter(boost::function<void (TObject*)> f)
{
    return boost::bind(_null_check_deleter<TObject>, _1, f);
}

void main()
{
    boost::shared_ptr<void> p(
        ::malloc(10),
        null_check_deleter<void>(::free));

    boost::shared_ptr<FILE> p2(
        ::fopen("hoge.txt", "r"),
        null_check_deleter<FILE>(::fclose));
}

自分でも意味が分からん……特に boost::bind の戻り値。デバッガで追いかけながらコピペしただけなのでほんと意味不明。