この記事は前回の記事(その仮想関数、本当に必要?)の続きです。
タイトルについて(3/24追記)
faithandbraveさんからの指摘を受けて、タイトルを修正しました。
はじめに
前回の記事で、タグ識別による動的なポリモーフィズムの実現について書きました。しかし問題点として、派生クラス上の呼びだされうるすべてのコンストラクタに、タグを設定する処理を書かなければなりませんでした。
どうしようかと悩んでいたところ、magicantさんがboost::variantを使うと良いと教えてくれたので、早速試してみました。
boost::variantによるタグ付け
boost::variantは、templateでいくつかの型を指定し、その中の任意の型の変数を保持できるクラスです。which()メソッドを呼び出すことで、今保持されている変数の型の、template上でのインデックスを教えてくれます。
boost::variant<int, char, bool> var[3] = {'a', false, 6};
std::cout << var[0].which() << std::endl; // 1
std::cout << var[1].which() << std::endl; // 2
std::cout << var[2].which() << std::endl; // 0
これを活用し、派生クラスを識別するためのタグを自動的に設定するようにしてみたいと思います。
基底クラスポインタの配列を扱うクラス
今回は、基底クラスのポインタの配列を扱えるクラスを実装します。どうせなので、このクラスに派生クラス識別用のタグも持たせることにします。
template <class BaseType, class... Ts> // Tsは派生クラスの型
class Base_Array {
protected:
std::unique_ptr<BaseType *[]> elems; // 基底クラスポインタの配列
public:
size_t length = 0;
std::unique_ptr<int[]> tags; // 派生クラス識別用のタグの配列
Base_Array() = delete;
Base_Array(size_t size)
: elems(new BaseType *[size]), tags(new int[size]){};
~Base_Array() {
for (size_t i = 0; i < length; ++i) {
delete elems[i];
}
}
template <class T>
void add(T *val) {
elems[length] = val;
boost::variant<Ts *...> tmp = val;
tags[length++] = tmp.which();
}
BaseType *&operator[](size_t index) { return elems[index]; }
}; //! class Base_Array
分かりにくくてあまり好ましくないコードですが、注目スべき部分はaddメソッド。一度boost::variantの変数に代入してから、which()メソッドでインデックスを求めています。
配列の生成と派生クラスのメンバ関数呼び出し部分はこんな感じ。
// 配列の生成
Base_Array<Base, Sub1, Sub2> arr(5000000);
for (int i = 0; i < 5000000; ++i) {
if (mt() % 2 == 0) { // mt()は整数乱数を生じる関数
arr.add(new Sub1());
} else {
arr.add(new Sub2());
}
}
// メソッド呼び出し
for (int i = 0; i < 5000000; ++i) {
switch (arr.tags[i]) {
case 0:
static_cast<Sub1 *>(arr[i])->test();
break;
case 1:
static_cast<Sub2 *>(arr[i])->test();
}
}
これで前回のタグ識別のプログラムとほぼ同じ速度になりました。
インデックス取得処理を実装
これでコンストラクタにタグ設定の処理を書く必要はなくなりました。もう腱鞘炎にならなくて済むね!
ですが、一度boost::variantに代入してwhich()メソッドを呼び出すという処理が無駄な感じがするので、この部分をスクラッチ実装したいと思います。
Variadic Templatesを用いた再帰を使えば以下のようになります。
template <class... Ts>
struct GetTypeIndex;
template <class Tp, class T1, class... Ts>
struct GetTypeIndex<Tp, T1, Ts...> {
static const int value = (std::is_same<Tp, T1>::value)
? 0
: 1 + GetTypeIndex<Tp, Ts...>::value;
};
template <class Tp>
struct GetTypeIndex<Tp>{
static const int value = -1;
};
要は、テンプレートの第2引数以降に与えられた型の中で、テンプレートの第一引数が一致する場所を見つけ出します。ごく普通のメタプログラミング。
これでもうboost::variantとはおさらば!
// Base_Array::add
template <class T>
void add(T *val) {
elems[length] = val;
tags[length++] = GetTypeIndex<T, Ts...>::value;
}
まとめ
これでboostも使わない、「自動タグ付け動的ポリモーフィズム」がとりあえず実現できました。まだまだC++初心者だからコードに無駄が多いような気がするので、今後も使えるように改善が必要ですね。
今回作成したソースコード(nonvirtual3.cpp)はこちらにあります。