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