I needed run-time polymorphism, so I used dynamic_cast
.
But now I had two problems -- dynamic_cast
was extremely slow! (Scroll down for benchmark.)
Long story short, I ended up solving the problem this way, using static_cast
:
struct Base
{
virtual ~Base() { }
virtual int type_id() const = 0;
template<class T>
T *as()
{ return this->type_id() == T::ID ? static_cast<T *>(this) : 0; }
template<class T>
T const *as() const
{ return this->type_id() == T::ID ? static_cast<T const *>(this) : 0; }
};
struct Derived : public Base
{
enum { ID = __COUNTER__ }; // warning: can cause ABI incompatibility
int type_id() const { return ID; }
};
int main()
{
Base const &value = Derived();
Derived const *p = value.as<Derived>(); // "static" dynamic_cast
}
But I'm surely not the first person to run into this problem, so I thought it might be worth asking:
Instead of coming up with a home-baked solution like this, is there a well-known pattern/library I can use for solving this problem in the future?
To get a feel for what I'm talking about, try the code below -- dynamic_cast
was approximately 15 times slower than a mere virtual
call on my machine (110 ms. against 1620 ms. with the code below):
#include <cstdio>
#include <ctime>
struct Base { virtual unsigned vcalc(unsigned i) const { return i * i + 1; } };
struct Derived1 : public Base
{ unsigned vcalc(unsigned i) const { return i * i + 2; } };
struct Derived2 : public Derived1
{ unsigned vcalc(unsigned i) const { return i * i + 3; } };
int main()
{
Base const &foo = Derived2();
size_t const COUNT = 50000000;
{
clock_t start = clock();
unsigned n = 0;
for (size_t i = 0; i < COUNT; i++)
n = foo.vcalc(n);
printf("virtual call: %d ms (result: %u)\n",
(int)((clock() - start) * 1000 / CLOCKS_PER_SEC), n);
fflush(stdout);
}
{
clock_t start = clock();
unsigned n = 0;
for (size_t i = 0; i < COUNT; i++)
n = dynamic_cast<Derived1 const &>(foo).vcalc(n);
printf("virtual call after dynamic_cast: %d ms (result: %u)\n",
(int)((clock() - start) * 1000 / CLOCKS_PER_SEC), n);
fflush(stdout);
}
return 0;
}
When I simply remove the word virtual
and change dynamic_cast
to static_cast
, I get a 79 ms running time -- so a virtual call is only slower than a static call by ~25%!
While typeid + static_cast is faster than dynamic_cast , not having to switch on the runtime type of the object is faster than any of them. Save this answer.
You are permitted to use dynamic_cast without --rtti in cases where RTTI is not required, such as dynamic cast to an unambiguous base, and dynamic cast to (void *) . If you try to use dynamic_cast without --rtti in cases where RTTI is required, the compiler generates an error.
static_cast − This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc. dynamic_cast −This cast is used for handling polymorphism.
This cast is used for handling polymorphism. You only need to use it when you're casting to a derived class. This is exclusively to be used in inheritence when you cast from base class to derived class.
Most uses of dynamic_cast
can be replaced with double dispatch (aka. visitor pattern). That would amount to two virtual calls, which by your benchmark is still 7.5 times faster than dynamic_cast
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With