配列の代わりに std::tuple を使って不要なキャストを避ける
上記の記事では、基底クラス型のポインタの配列に対して、 任意の添え字に対して静的に型が決っている派生クラスのアドレスを保持し、 テンプレート関数を用いて派生クラスポインタ型のアドレスを取得している。
この記事を見てなんか違和感があったのでよく考えてみたのだが、 配列に入るオブジェクトの型をコンパイル時に決定しているのなら、 キャストをせずにアドレスを取得する事が可能な筈だ。
std::tuple
を用いてアドレスを保持すれば良い。
上記の記事のコードを、単純にstd::tuple
を用いたコードに変更すると、以下のようになる。
#include <iostream> #include <tuple> enum class TYPE_NUMBER { A, B, C, TYPE_MAX }; struct A { void Print() { std::cout << "A" << std::endl; } }; struct B : public A { void Print() { std::cout << "B" << std::endl; } }; struct C : public A { void Print() { std::cout << "C" << std::endl; } }; std::tuple<A*, B*, C*> ptrTuple; int main(void) { using namespace std; get<static_cast<size_t>(TYPE_NUMBER::A)>(ptrTuple) = new A(); get<static_cast<size_t>(TYPE_NUMBER::B)>(ptrTuple) = new B(); get<static_cast<size_t>(TYPE_NUMBER::C)>(ptrTuple) = new C(); auto Aptr = get<static_cast<size_t>(TYPE_NUMBER::A)>(ptrTuple); auto Bptr = get<static_cast<size_t>(TYPE_NUMBER::B)>(ptrTuple); auto Cptr = get<static_cast<size_t>(TYPE_NUMBER::C)>(ptrTuple); cout << Aptr << endl; cout << Bptr << endl; cout << Cptr << endl; Aptr->Print(); Bptr->Print(); Cptr->Print(); delete Aptr; delete Bptr; delete Cptr; return 0; }
ptrTuple
はローカル変数にしてもよい。
もっと言えば、型をauto
にしておいて、std::make_tuple
関数に動的確保したアドレスを渡せば、型の記述を省略できる。
int main(void) { ... auto ptrTuple = make_tuple(new A(), new B(), new C()); auto Aptr = get<static_cast<size_t>(TYPE_NUMBER::A)>(ptrTuple); ... }
上記のコードであれば、一切のキャストが発生しない。
しかしながら、上記のコードは元のコードとは異なり、range-based for
によるdelete
が実現できていない。
std::tuple
が保持する要素に対するアクセスは、std::get
関数で行われるが、
この関数はテンプレート関数であるため、コンパイル時定数を用いてアクセスする必要があるからだ。
このため、通常の配列と比較すると、要素へのアクセスがやや面倒な場面がある。
キャストや、仮想関数のコールが大きなボトルネックにならないのであれば、
このような場面でstd::tuple
を用いて実装する必要性は薄いかもしれない。
次回の記事では、std::tuple
に対してstd::for_each
的な処理を行いたい。