Boost.Variantにpolymorphic_getという関数が追加されていることに気が付きました。リリースノートを見ると1.56.0で追加されたようです(1.56.0 日本語、1.56.0 英語)。
boost::getとよく似たこの関数は、しかしboost::getと異なり、基底クラスの型を指定しても値を取り出せるというものです。
#include <iostream> #include <boost/variant/variant.hpp> #include <boost/variant/polymorphic_get.hpp> struct Base { Base(std::string s) : name(std::move(s)) {} std::string name; }; struct ImplA : Base { using Base::Base; }; int main() { boost::variant<int, ImplA> x = ImplA("mikan"); std::cout << boost::polymorphic_get<ImplA>(x).name << std::endl; std::cout << boost::polymorphic_get<Base>(x).name << std::endl; } |
このプログラムを実行すると、2回目のBaseを指定したboost::polymorphic_get関数の呼び出しも成功します。そのため、次のような出力になります。
mikan mikan
boost::variantを参照で受け取るものと、ポインタで受け取るものが用意されている点は、boost::getと同じです。すなわち、指定した型で値を取り出せない場合、参照だと例外が投げられ、ポインタだとnullptrが返されます。
これを使って、多態的なコードが書けます。
#include <iostream> #include <ctime> #include <boost/variant/variant.hpp> #include <boost/variant/polymorphic_get.hpp> struct Base { virtual void Do() = 0; }; struct ImplA : Base { void Do() override { std::cout << "ImplA" << std::endl; } }; struct ImplB : Base { void Do() override { std::cout << "ImplB" << std::endl; } }; int main() { boost::variant<ImplA, ImplB> obj; if (std::time(nullptr) % 2 == 1) // ランダムに振り分ける意図 { obj = ImplA(); } else { obj = ImplB(); } boost::polymorphic_get<Base>(obj).Do(); } |
main関数内の変数objは、boost::variantによりImplAまたはImplBの型のオブジェクトを保持します。ImplAとImplB、ともにBase型から派生させています。するとどうでしょう、boost::polymorphic_get<Base>(obj)
と書くと、「objの中身が何型か分からないけど、とにかくBase型(への参照)としてアクセスする」ということが実現できます。
ポインタを使った、このようなコードなら馴染みある形ではないでしょうか。それと似たようなことがBoost.Variantで書けたということになります。
int main() { std::unique_ptr<Base> obj; if (std::time(nullptr) % 2 == 1) // ランダムに振り分ける意図 { obj = std::make_unique<ImplA>(); } else { obj = std::make_unique<ImplB>(); } obj->Do(); } |
先のようなBoost.Variantを使うやり方は、ポインタを使う場合と比べ、次のような点が異なります。
- 動的メモリ確保が不要。ただし、すべての型のレイアウトが分かっていないといけない(pimplイディオムのように、実装詳細として隠せない)。
- boost::variantのテンプレート実引数で、使用が想定される型をすべて書かないといけない。コンパイル時に決める必要がある。
私は、動的メモリ確保が不要な点で、今回これは便利だと思いました。
もちろん、boost::polymorphic_get関数がないとこういう多態的なコードが書けない、というわけではありません。そもそも、私は初めこういうコードを書こうとしていました。
int main() { boost::variant<ImplA, ImplB> obj; Base* p = nullptr; if (std::time(nullptr) % 2 == 1) { obj = ImplA(); p = boost::get<ImplA>(&obj); } else { obj = ImplB(); p = boost::get<ImplB>(&obj); } p->Do(); } |
ふとBoost.Variantのリファレンスを見たら、boost::polymorphic_getを見つけたという次第です。
2016年2月29日追記: この方法は、基底型(上記ではBase型)へのポインタや参照を取り出し、virutal関数による多態性を利用するコードに混ぜて使えるのが特徴です。その必要が無い(Boost.Variantで統一できる)なら、virtual関数を作らず、apply_visitor関数とテンプレート(またはジェネリックラムダ)を使う方法もあります。
スポンサード リンク |
この記事のカテゴリ
- C++ ⇒ Boost.Variantのpolymorphic_get関数