2007-02-20

Inside Boost.Any

 Boost.Anyを使えば、次のようなコードが書ける。
int main()
{
    std::vector<boost::any> v ;

    for (int i = 0 ; i != 20 ; ++i) {
        switch(rand()%5)
        {
        case 0 : // int型の値
            v.push_back(int(123456)) ;
            break ;
        case 1 : //円周率でも入れてみよう
            v.push_back(float(3.14f)) ;
            break ;
        case 2 : //精度が高くても大丈夫だろう
            v.push_back(double(3.141592)) ;
            break ;
        case 3 : //std::stringはどうかな
            v.push_back(std::string("hello,world")) ;
            break ;
        case 4 : //vectorなんていれてみたりして
            v.push_back(std::vector<int>(10, 0)) ;
        }
    }


    for (std::vector<boost::any>::iterator iter = v.begin(), end = v.end() ; iter != end ; ++iter)
    {
        std::type_info const & t = iter->type() ;
        if (t == typeid(int))
        { std::cout << "int : " << boost::any_cast<int>(*iter) << std::endl ; }
        else if (t == typeid(float) )
        { std::cout << "float : " << boost::any_cast<float>(*iter) << std::endl ; }
        else if (t == typeid(double))
        { std::cout << "double : " << boost::any_cast<double>(*iter) << std::endl ; }
        else if (t == typeid(std::string))
        { std::cout << "string : " << boost::any_cast<std::string>(*iter) << std::endl ; }
        else if (t == typeid(std::vector<int>))
        { std::cout << "std::vector size : " << boost::any_cast< std::vector<int> >(*iter).size() << std::endl ; }
    }
}

これが、あの型に厳しいC++のコードだろうか。見ての通り、anyには、ほぼどんな型でも入れることができる。(CopyConstructibleで、例外を投げないAssignableで、例外を投げないデストラクタでさえあれば)

なにしろ、かの変態的なBoostのライブラリである。さぞかし不可思議なコードになっていることであろうと考えてしまうかもしれないが、実は違う。ものすごく、コロンブスの卵的な、単純なコードで実現されている。どうもanyの実装に関する日本語のWebサイトが引っかからないので、この際、ここで解説を試みる。おそらくは、私のようにanyの実装が気になっていて、自分では実装方法がわからない人は、日本にひとりぐらいはいると思われる。うむ、居るのではないかな。居るかもしれない。

とはいっても、仕組みを知ってしまうと、解説する気さえ失せてしまうのだが。

型を無視したい場合、一体どうするか。たとえば、オブジェクトへのポインタを、void *型にキャストしてしまう方法がある。環境によっては、うまくいくだろうが、これではあまりにも貧弱すぎる。第一、anyのように、どんな型でも入れられるような機能を実装しようとすると、今何を入れているのか、把握しておかなければならない。ではどうするか。

ところで、C++には仮想関数という便利な仕組みが存在する。Baseクラスで純粋仮想関数を定義しておき、それを継承したクラスを作る。そして、Baseクラスのポインタとして呼び出すと、なんと、自分の型の関数が呼ばれるという、なんとも便利な機能だ。

class Dwarf
{
public :
    virtual void show_yourself(void) = 0 ;
} ;

class Dwalin : public Dwarf
{
public :
    virtual void show_yourself(void)
    { std::cout << "Dwalin at your service!" << std::endl ; }
} ;
class Balin : public Dwarf
{
public :
    virtual void show_yourself(void)
    { std::cout << "Balin at your service!" << std::endl ; }
} ;
class Kili : public Dwarf
{
public :
    virtual void show_yourself(void)
    { std::cout << "Kili at your service!" << std::endl ; }
} ;

このように、ドワーフクラスを継承した、各ドワーフ達に、挨拶をさせることができる。

しかし悲しいかな、現実は、往々にしてうまくいかないものだ。既存のドワーフクラスがあり、それらを再利用したいとする。それぞれ、てんでばらばらである。共通のベースクラスを継承しているなどということはない。ただし、show_yourselfメンバ関数で、挨拶をすることだけは、共通している。

// 汚い既存の現実のコード
// それぞれ、異なる思想で設計されている。
// ただし、show_yourself関数だけは共通
class Dwalin 
{
public :
    void show_yourself(void)
    { std::cout << "Dwalin at your service!" << std::endl ; }
} ;
class Balin
{
public :
    int a, b, c, d, e, f, g ;
    void show_yourself(void)
    { std::cout << "Balin at your service!" << std::endl ; }
} ;
class Kili : class CommonObject
{
public :
    virtual void CommonWhat() ;
    virtual void CommonFunc() ;
    virtual void show_yourself(void)
    { std::cout << "Kili at your service!" << std::endl ; }
} ;

中つ国の住人ならば既にお気づきかと思われるが、これはホビットの冒険に出てくるドワーフ達である。つまり、こんなクラスが13個もあるのだ(ドワーフは13人いる) このクラスのオブジェクトを管理し。show_yourself関数を呼び出したいとする。実行時にオブジェクトを管理しなければならないので、コンパイル時に型が決まってしまうテンプレートでは役不足である。実行時にドワーフを入れ替えたいのだ。さて、どうしよう。

こういうとき、どんなドワーフでも代入できる、any_dwarfなるクラスがあれば便利だ。早速作ってみよう。

実装のミソは、仮想関数である。

class placeholder
{
public :
    virtual void show_yourself(void) = 0 ;
} ;

template < typename DwarfType >
class holder
{
public :
    holder(DwarfType const & dwarf)
        : held(dwarf) { }

    virtual void show_yourself(void)
    {
        held.show_yourself() ;
    }
private :
    DwarfType held ;
} ;
 基本はこれ。
class Dwalin 
{
public :
    void show_yourself(void)
    { std::cout << "Dwalin at your service!" << std::endl ; }
} ;
class Balin
{
public :
    void show_yourself(void)
    { std::cout << "Balin at your service!" << std::endl ; }
} ;
class Kili
{
public :
    virtual void show_yourself(void)
    { std::cout << "Kili at your service!" << std::endl ; }
} ;

class any_dwarf
{
private :
    class placeholder
    {
    public : // structers
        virtual ~placeholder(){}
    public : // queries
        virtual placeholder * clone(void) = 0 ;
    public : // member function
        virtual void show_yourself(void) = 0 ;
    } ;

    template < typename DwarfType >
    class holder : public placeholder
    {
    public :
        explicit holder(DwarfType const & dwarf)
            : held(dwarf)
        { }
        virtual placeholder * clone(void)
        {
            return new holder(held) ;
        }
        virtual void show_yourself(void)
        {
            held.show_yourself() ;
        }
    private :
        DwarfType held ;
    } ;

public :
    template < typename DwarfType >
    any_dwarf(DwarfType const & dwarf)
        : content(new holder<DwarfType>(dwarf) )
    { }

    any_dwarf(any_dwarf const & other)
        : content(other.content.get() ? other.content->clone() : 0)
    { }

    any_dwarf & operator = (any_dwarf const & rhs)
    {
        if (this != &rhs)
        { content.reset(rhs.content->clone()) ; }
        return *this ;
    }

    template < typename DwarfType >
    any_dwarf & operator = (DwarfType const & rhs)
    {
        content.reset(new holder<DwarfType>(rhs)) ;
        return *this ;
    }

    void show_yourself(void)
    {
        content->show_yourself() ;
    }



private :
    std::auto_ptr<placeholder> content ;
} ;



int main()
{
    std::vector<any_dwarf> v ;

    for (int i = 0 ; i != 20 ; ++i)
    {
        switch(rand()%3)
        {
        case 0 :
            v.push_back(Dwalin()) ;
            break ;
        case 1 :
            v.push_back(Balin()) ;
            break ;
        case 2 :
            v.push_back(Kili()) ;
            break ;
        }
    }

    for (std::vector<any_dwarf>::iterator iter = v.begin(), end = v.end() ;
        iter != end ; ++iter )
    {
        iter->show_yourself() ;
    }
}

まず、placeholderという抽象クラスがある。これを継承するテンプレートなクラス、holderが、それぞれの型のオブジェクトを持つ。any_dwarf自体は、holderへのポインタを持てばよい。こうすることによって、実行時に型を変えることができる。そして、仮想関数のディスパッチによって、正しいshow_yourself関数が呼ばれることになる。

Boostのanyも、これと同じ実装である。

実際にコードを示したほうが分かりやすいと思ったが、まさかこんな例を使うことになるとは思わなかった。しかし、Type Erasureは、よくよく考えると当たり前の挙動だ。何故今まで思いつかなかったのだろう。実装も実にたやすい。コードも短い。

肝心のany_iteratorだが、なんだか結構複雑だ。何故あんなに複雑なのだろう。確かにイテレータには、種類があるとしても、何故だろう。今なら、any_iteratorのコードも読めそうな気がする。

それにしても、まさか自分で、anyを実装できるようになるとは思わなかった。C++ Template Metaprogrammingという本はすばらしい。

 参考:C++ Template Metaprogramming

No comments:

Post a Comment

You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.