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.