My question refers to:
C++ dynamic_cast vs storing object type in a static enum?
where the question has been left unanswered. dynamic_cast requires RTTI, whereas virtual functions require a table lookup, slowing down their invocations (I know Stroustrup recommends this). Is an enum + an accessor the fastest way to discern between types?
EDIT:
The point of my post was a class Screen:
class ScreenImpl;
class Screen
{
public:
enum Type
{
GLES1,
GLES2,
GLES3
};
enum Type type() const noexcept { return type_; }
private:
enum Type type_;
ScreenImpl* impl_;
};
The class can have different implementations (using PIMPL, one of 3 different contexts can be created, the header file stays the same, so a static_cast is ok) and I thought objects might query under what context they are running (GLES1, GLES2 or GLES3). Alternatively, I could have used a dynamic_cast (needs at least 1 virtual member function) or typeid. Now, after reading the posts I think I will do away with this and have all objects know under what context they are running in advance (foregoing all ifs and switches, as well as invocations of virtual functions).
When in doubt, profile and check assembly :)
Here is a comparison of the three cases (with a dynamic_cast, with a virtual fuction (type) and with an enum). For the sake of simplicity, the special method is the same in all cases to only see the difference in real cases. However, in a real world setting, "special" would be something different each time, else there would be no point.
The by_enum also provides a demonstration of what happens in a vcall at the beginning.
enum Type { D1_t, D2_t, D3_t, D4_t, D5_t };
struct Base
{
virtual ~Base() = default;
virtual Type type() = 0;
};
struct D1 : public Base
{
Type type() override { return D1_t; }
int special1();
};
struct D2 : public Base{
Type type() override { return D2_t; }
int special2();
};
struct D3 : public D2{
Type type() override { return D3_t; }
int special3();
};
struct D4 : public D2{
Type type() override { return D4_t; }
int special4();
};
struct D5 : public D4{
Type type() override { return D5_t; }
int special5();
};
int by_dynamic(Base* b)
{
if(auto d = dynamic_cast<D1*>(b)) return d->special1();
else if(auto d = dynamic_cast<D2*>(b)) return d->special2();
else if(auto d = dynamic_cast<D3*>(b)) return d->special3();
else if(auto d = dynamic_cast<D4*>(b)) return d->special4();
else if(auto d = dynamic_cast<D5*>(b)) return d->special5();
}
int by_enum(Base* b)
{
switch(b->type())
{
case D1_t:
return static_cast<D1*>(b)->special1();
break;
case D2_t:
return static_cast<D2*>(b)->special2();
break;
case D3_t:
return static_cast<D3*>(b)->special3();
break;
case D4_t:
return static_cast<D4*>(b)->special4();
break;
case D5_t:
return static_cast<D5*>(b)->special5();
break;
}
}
Here is the relevant ASM for by_dynamic (GCC-5.2, -O3). So I'd argue that if you're performance-constrained, go for the enum.
by_dynamic(Base*):
testq %rdi, %rdi
je .L15
pushq %rbx
xorl %ecx, %ecx
movl typeinfo for D1, %edx
movl typeinfo for Base, %esi
movq %rdi, %rbx
call __dynamic_cast
testq %rax, %rax
je .L3
popq %rbx
movq %rax, %rdi
jmp D1::special1()
.L3:
xorl %ecx, %ecx
movl typeinfo for D2, %edx
movl typeinfo for Base, %esi
movq %rbx, %rdi
call __dynamic_cast
testq %rax, %rax
je .L4
popq %rbx
movq %rax, %rdi
jmp D2::special2()
.L4:
xorl %ecx, %ecx
movl typeinfo for D3, %edx
movl typeinfo for Base, %esi
movq %rbx, %rdi
call __dynamic_cast
testq %rax, %rax
je .L5
popq %rbx
movq %rax, %rdi
jmp D3::special3()
.L5:
xorl %ecx, %ecx
movl typeinfo for D4, %edx
movl typeinfo for Base, %esi
movq %rbx, %rdi
call __dynamic_cast
testq %rax, %rax
je .L6
popq %rbx
movq %rax, %rdi
jmp D4::special4()
.L6:
xorl %ecx, %ecx
movl typeinfo for D5, %edx
movl typeinfo for Base, %esi
movq %rbx, %rdi
call __dynamic_cast
testq %rax, %rax
je .L2
popq %rbx
movq %rax, %rdi
jmp D5::special5()
.L2:
popq %rbx
.L15:
ret
And for by_enum
by_enum(Base*):
pushq %rbx
movq (%rdi), %rax
movq %rdi, %rbx
call *16(%rax)
cmpl $4, %eax
ja .L18
movl %eax, %eax
movq %rbx, %rdi
jmp *.L20(,%rax,8)
.L20:
.quad .L19
.quad .L21
.quad .L22
.quad .L23
.quad .L24
popq %rbx
jmp D4::special4()
popq %rbx
jmp D5::special5()
popq %rbx
jmp D1::special1()
popq %rbx
jmp D2::special2()
popq %rbx
jmp D3::special3()
.L18:
popq %rbx
ret
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