C++ idiom for base class abstract methods without dynamic dispatch overhead?
In C++, is there any way to have an "abstract" base class method (i.e.,
declared and callable from the base class, but implemented in subclasses)
without declaring the method as virtual?
This question, of course, only applies to cases where polymorphism isn't
needed (pointers/references to base types never used). Consider the
following:
#define NO_OPT asm volatile (""); // to prevent some compiler optimization
template<typename DerivedType>
void doSomething(DerivedType& d) { d.foo(); }
namespace test1 {
struct Base {
inline void foo()
{
// ... do common stuff pre-call ...
foo_impl();
// ... do common stuff post-call ...
}
virtual void foo_impl() = 0; // the abstract method
};
struct D1 : public Base { virtual void foo_impl() final { NO_OPT } };
struct D2 : public Base { virtual void foo_impl() final { NO_OPT } };
// Then the usage of D1, D2, ..., DN, could be as follows:
void go() {
D1 d1; doSomething(d1);
D2 d2; doSomething(d2);
for ( long i=0; i < 5000000000; i++ ) {
// this loop takes [9] seconds
doSomething(d2);
}
}
}
Note that polymorphism is not needed in this case, and that there are
plenty of optimization opportunities for the compiler.
However, I benchmarked this code in the latest g++ (4.8.2) and clang (3.4)
with -O3 optimizations enabled, including link-time (LTO), and it was
significantly slower than the following alternative implementation (using
templates instead of virtual method):
namespace test2 {
template<typename DerivedType>
struct Base : public DerivedType // inheritance in reverse
{
inline void foo()
{
// ... do common stuff pre-call ...
DerivedType::foo_impl();
// ... do common stuff post-call ...
}
};
struct D1 { void foo_impl() { NO_OPT } };
struct D2 { void foo_impl() { NO_OPT } };
void go() {
Base<D1> d1; doSomething(d1);
Base<D2> d2; doSomething(d2);
for ( long i=0; i < 5000000000; i++ ) {
// this loop takes [3] seconds
doSomething(d2);
}
}
}
g++ and clang were remarkably consistent, each compiling optimized code
that took 9 seconds to execute the test1 loop, but only 3 seconds to
execute the test2 loop. So even though the test1 logic has no need for
dynamic dispatch as all calls should be able to be resolved at compile
time, it is still significantly slower.
So to restate my question: When polymorphism isn't needed, Is there a way
to achieve this "abstract method" behavior using the convenient
straight-forward class inheritance (like in test1), but without the
performance penalty of virtual functions (as achieved in test2)?
No comments:
Post a Comment