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関数とテンプレート(またはジェネリックラムダ)を使う方法もあります。


スポンサード リンク

この記事のカテゴリ

  • ⇒ Boost.Variantのpolymorphic_get関数