【C++ Advent Calendar 2012】Boost.TypeErasure【2日目(後編)】
C++ Advent Calendar 2012 2日目の後編の記事になります。
前編はこちら。
概要
Boost.TypeErasure の布教を簡単に。
C++ での一般的なポリモフィズム
C++ でポリモフィズムを実現する場合、一般的には継承を使用して次のような感じになるかと思います。
#include <iostream> #include <memory> #include <vector> struct animal{ void virtual say() const = 0; virtual ~animal(){} }; struct inu : animal{ void virtual say() const override{ std::cout << "(U^ω^)わんわんお!" << std::endl; } }; struct tori : animal{ void virtual say() const override{ std::cout << "ヾ ゚∋゚)ノシ クペー クペー" << std::endl; } }; int main(){ std::vector<std::shared_ptr<animal>> animals; animals.push_back(std::make_shared<inu>()); animals.push_back(std::make_shared<tori>()); for(auto&& a : animals){ a->say(); } return 0; } /* output: (U^ω^)わんわんお! ヾ ゚∋゚)ノシ クペー クペー */
animal クラスをインターフェースとして使用し、継承側のクラスで処理を実装という割りと一般的な継承の使い方ですね。
しかし、このように継承を使用した場合では animal クラスを継承したクラスオブジェクトしか保持できないという欠点があります。
struct neko{ void virtual say() const override{ std::cout << "(「・ω・)「にゃー" << std::endl; } }; // animal クラスを継承してないけどクラスオブジェクトを保持したい!! animals.push_back(std::make_shared<neko>());
この欠点を緩和して使用することが出来るのが Boost.TypeErasure になります。
Boost.TypeErasure とは
Boost.TypeErasure とは継承に依存しないで定義したコンセプトを満たすクラスオブジェクトを保持することが出来るライブラリです。
また、コンセプトを通じて保持しているクラスオブジェクトを簡単に操作することも出来ます。
既存のライブラリでは Boost.Any や Boost.Function がそれに近いかと思います。
Boost.TypeErasure は採用されることは決まりましたが、まだ正式な Boostのライブラリではない事に注意して下さい。
(その為、下記で提示したコードは正式にリリースされた場合に変更されている可能性があります。
Boost.TypeErasure を使ってみる
言葉にするとちょっとわかりづらいかと思うので実際に書いてみましょう。
簡単に使用してみるとこんな感じになります。
#include <boost/type_erasure/any_cast.hpp> #include <boost/type_erasure/operators.hpp> #include <boost/type_erasure/operators.hpp> #include <boost/mpl/vector.hpp> #include <iostream> #include <string> #include <vector> struct X{ int value; }; X operator +(X const& a, X const& b){ return X{ a.value + b.value }; } std::ostream& operator <<(std::ostream& os, X const& x){ return os << x.value; } int main(){ namespace te = boost::type_erasure; // boost::type_erasure::any が保持するクラスオブジェクトのコンセプトを決める typedef boost::mpl::vector< // 加算コンセプト te::addable<>, // std::ostream への出力コンセプト te::ostreamable<>, // コピーコンストラクタコンセプト te::copy_constructible<> > requirements; // requirements で定義されたコンセプトを満たすクラスオブジェクトを保持できる typedef te::any<requirements> any; any hoge{12}; std::cout << hoge << std::endl; std::cout << hoge + hoge << std::endl; std::vector<any> objects; // any へ暗黙的に型変換出来ないので emplace_back を使用 objects.emplace_back(3.14f); objects.emplace_back(std::string("homu")); objects.emplace_back(X{42}); // requirements を満たしていないのでエラー // objects.emplace_back({1, 2, 3}); for(auto&& object : objects){ std::cout << object << std::endl; std::cout << object + object << std::endl; } return 0; } /* output: 12 24 3.14 6.28 homu homuhomu 42 84 */
コード見てもらえればなんとなく何をやっているのかがわかると思います。
最初に保持するクラスオブジェクトのコンセプトを決めて、そのコンセプトを満たすクラスオブジェクトを保持することが出来ます。
もちろん、コンセプトを満たさないクラスオブジェクトを保持しようとすると コンパイルエラーとなります。
指定できるコンセプトは Boost.TypeErasure 内いくつか用意されているのでそれを使用することが出来ます。
また、クラスオブジェクトに要求するコンセプトはユーザ側でも定義することが出来ます(下記に続く。
ユーザ側でコンセプトを定義する
先ほどの継承を使用したポリモフィズムは下記のように書くことが出来ます。
クラスオブジェクトに定義されているメンバ関数を要求するのがポイントですね。
#include <boost/type_erasure/any_cast.hpp> #include <boost/type_erasure/member.hpp> #include <boost/mpl/vector.hpp> #include <iostream> #include <string> #include <vector> // say メンバ関数を持っているかどうかをチェックするコンセプトを定義する BOOST_TYPE_ERASURE_MEMBER((has_say), say, 0) // say メンバ関数が存在すれば animal オブジェクトである typedef boost::type_erasure::any< boost::mpl::vector< // コンセプト has_say<void()>, boost::type_erasure::copy_constructible<> > > animal; // 継承が必要なく各クラスに say メンバ関数を定義するだけ struct inu{ void say() const{ std::cout << "(U^ω^)わんわんお!" << std::endl; } }; struct tori{ void say() const{ std::cout << "ヾ ゚∋゚)ノシ クペー クペー" << std::endl; } }; struct neko{ void say() const{ std::cout << "(「・ω・)「にゃー" << std::endl; } }; int main(){ std::vector<animal> animals; // say メンバ関数を持っているクラスであればなんでも保持することが出来る animals.emplace_back(inu{}); animals.emplace_back(tori{}); animals.emplace_back(neko{}); for(auto&& a : animals){ a.say(); } return 0; } /* output: (U^ω^)わんわんお! ヾ ゚∋゚)ノシ クペー クペー (「・ω・)「にゃー */
継承も仮想関数も必要がないので実装側のクラスはだいぶすっきりした書き方になっていますね。
まとめ
簡単ですが Boost.TypeErasure について紹介してみました。
継承を使用しないでポリモフィズムが実現出来るのは大きいと思います。
コンセプトを変更することで柔軟に対応できますしね。
今回はポリモフィズムの例だけでしたが他にもまだ利用できそうな点はあるかと思います。
個人的にはこれが結構便利なんじゃな以下と思っています。
まだ、trunk にすら入っていませんが、Boost.TypeErasure が正式にリリースされるのが楽しみですね!!
関連記事
- Boost.TypeErasure を使ってみた
- Boost.TypeErasure でメンバ関数の呼び出しと定義x
- BOOST_TYPE_ERASURE_MEMBER
- Boost.TypeErasure でメンバ変数っぽいのを定義する
- Boost.TypeErasure でタスク処理
- Boost.TypeErasure でダウンキャスト
- Boost.TypeErasure で特定の関数で多重定義されているオブジェクトを保持する
- Boost.TypeErasure で多重定義の呼び出し
- Boost.TypeErasure の any は暗黙の型変換が行われない
- Boost.TypeErasure の any でデフォルトコンストラクタを使用する
次回予告
明日の C++ Advent Calendar 2012 は id:Suikaba さんです。
Boost.Contract はわたしも気になっているので楽しみですね!!