There are various overheads:
- Size : The instances of such classes take more space
- Time : Calling virtual methods takes time
Consider the code below:
using namespace std;
class VirtualObject
{
public:
virtual int foo (int i, int j) = 0;
virtual ~VirtualObject()
{
}
};
class DerivedVirtual : public VirtualObject
{
private:
int x;
public:
int foo (int i, int j) {
++j;
return j;
}
};
class ConcreteObject
{
public:
int foo (int i, int j)
{
return j;
}
};
class DerivedConcrete : public ConcreteObject
{
private:
int x;
public:
int foo (int i, int j) {
++j;
return j;
}
};
void test_virtual_object_size ()
{
cout << "sizeof(VirtualObject): " ;
cout << sizeof(VirtualObject) << endl;
cout << "sizeof(DerivedVirtual): " ;
cout << sizeof(DerivedVirtual) << endl;
cout << "sizeof(ConcreteObject): " ;
cout << sizeof(ConcreteObject) << endl;
cout << "sizeof(DerivedConcrete): " ;
cout << sizeof(DerivedConcrete) << endl;
}
#define TEST_INNER_LOOP 1000000
#define TEST_OUTER_LOOP 100
void test_virtual_call_overhead ()
{
Core::Timer timer;
long long x;
x = 0;
DerivedVirtual dv;
VirtualObject & vo = dv;
timer.start();
for ( int i=0; i < TEST_OUTER_LOOP; ++i )
{
for ( int j=0; j < TEST_INNER_LOOP; ++j )
{
x += vo.foo(i,j);
}
}
cout << "DerivedVirtual::foo() " << x << " " << timer.elapsedTimeInSeconds() << endl;
x = 0;
DerivedConcrete dc;
timer.start();
for ( int i=0; i < TEST_OUTER_LOOP; ++i )
{
for ( int j=0; j < TEST_INNER_LOOP; ++j )
{
x += dc.foo(j,i);
}
}
cout << "DerivedConcrete::foo() " << x << " " << timer.elapsedTimeInSeconds() << endl;
}
In my system I get the following output for test_virtual_object_size
sizeof(VirtualObject): 4
sizeof(DerivedVirtual): 8
sizeof(ConcreteObject): 1
sizeof(DerivedConcrete): 4
This essentially means 4 byte for each object. It can be a significant overhead especially for small size objects.
For virtual call overhead this is what I get on release mode:
DerivedVirtual::foo() 50000050000000 0.423278
DerivedConcrete::foo() 5050000000 1e-06
But on debug mode this is what I get:
DerivedVirtual::foo() 50000050000000 0.832059
DerivedConcrete::foo() 5050000000 0.710893
The difference is mostly because of inlining. The compiler can not inline virtual functions, thus the overhead becomes significant in release mode.
One last note is that you incur the virtual call overhead only if you call the virtual function through a pointer or reference. This is important. If we used dv, instead of vo within the loop we would have identical results as the non-virtual function call. However, notice that dv is a DerivedVirtual object, not a pointer. If you write a function like the following, you will again incur the virtual call overhead.
void test1 ( DerivedVirtual & pdv )In this case, we would incur the virtual function overhead. Instead of using a reference to DerivedVirtual, if we had used a DerivedVirtual object we would not have the virtual call overhead.
{
Core::Timer timer;
long long x;
x = 0;
timer.start();
for ( int i=0; i < TEST_OUTER_LOOP; ++i )
{
for ( int j=0; j < TEST_INNER_LOOP; ++j )
{
x += pdv.foo(i,j);
}
}
cout << "DerivedVirtual::foo() " << x << " " << timer.elapsedTimeInMilliSeconds() << endl;
}
If C++ had a facility to declare a class as final or sealed, the compiler could have used that information to optimize these virtual calls, since pdv.foo had to be the DerivedVirtual's foo and nothing else. Even though there is no other class deriving from DerivedVirtual, we still see the virtual call overhead.