воскресенье, 19 мая 2013 г.

is_type, dynamic_cast, double dispatch и производительность

Как то раз я встретил реализацию шаблонного класса для хранения объектов разных типов, которая мне не понравилась. Если упростить ситуацию, то этот класс выглядит так:
struct val_base
{
    virtual ~val_base() = 0;
    template<typename T> bool is_type() const;
};
template <typename T> struct val : public val_base
{
    T value_;
};
Класс этот общего назначения и поэтому не представляется возможным определить сразу набор виртуальных функции для операций с его потомками. Поэтому используется функции is_type<T>  и работа с экземпляром класса выглядит таким образом
val_base * v;
...
if(v->is_type<int>())
{
   int val = static_cast<val<int> *>(v)->value_;
...
}
else if(v->is_type<double>())
{
   double val = static_cast<val<double> *>(v)->value_;
...
}
...
Реализована функция is_type<T>  так:
template<typename T> bool val_base::is_type() const 
{
      return dynamic_cast<const val<T>*>(this) != NULL;
}
Мне это не понравилось, так как были подозрения что эта реализация имеет серьёзные проблемы с производительностью. Во первых производятся множественные вызовы функции  is_type, во-вторых использование dynamic_cast  даже в этом простейшем случае может быть довольно накладным. Кроме того есть и другие причины не любить такой метод использования этого класса - довольно сложно сопровождать это решение в случае расширения списка хранимых типов, да и это просто некрасиво. Дальнейший текст посвящен измерению производительности различных методов работы с этим классом. Забегая вперед - метод работы, использующий is_type<T> реализованный с помощью  dynamic_cast, действительно самый медленный, более быстрые методы могут работать в 10 раз быстрее.