Does one virtual function slow down an entire class?
Or just a virtual function call? And does speed affect if the virtual function is actually overwritten or not, or does it have no effect as long as it is virtual.
The presence of virtual functions slows down the entire class, since another data element must be initialized, copied ... when working with an object of such a class. For a class with half a dozen or so members, the difference should be careless. For a class that contains only one char member or has no members at all, the difference can be noticeable.
In addition, it is important to note that not every virtual function call is a virtual function call. If you have an object of a known type, the compiler can emit code for a normal function call and can even embed the specified function if it likes it. This only happens when you make polymorphic calls using a pointer or link that can point to an object of a base class or an object of some derived class, for which you need a vtable link and pay for it in terms of performance.
struct Foo { virtual ~Foo(); virtual int a() { return 1; } }; struct Bar: public Foo { int a() { return 2; } }; void f(Foo& arg) { Foo x; xa();
The steps to be taken are essentially the same, regardless of whether this function is overwritten or not. The vtable address is read from the object, the function pointer obtained from the corresponding slot, and the function called by the pointer. In terms of actual performance, industry forecasts may have some effect. So, for example, if most of your objects are related to the same implementation of this virtual function, then it is likely that the branch predictor will correctly predict which function to call before the pointer is found. But it doesn’t matter which function is common: most objects can delegate an unwritten base case or most objects belonging to the same subclass, and therefore delegate the same rewritten case.
How are they implemented at a deep level?
I like jheriko's idea of ​​demonstrating this with a mock implementation. But I would use C to implement something similar to the code above, so the low level is easier to see.
parent class foo
typedef struct Foo_t Foo; // forward declaration struct slotsFoo { // list all virtual functions of Foo const void *parentVtable; // (single) inheritance void (*destructor)(Foo*); // virtual destructor Foo::~Foo int (*a)(Foo*); // virtual function Foo::a }; struct Foo_t { // class Foo const struct slotsFoo* vtable; // each instance points to vtable }; void destructFoo(Foo* self) { } // Foo::~Foo int aFoo(Foo* self) { return 1; } // Foo::a() const struct slotsFoo vtableFoo = { // only one constant table 0, // no parent class destructFoo, aFoo }; void constructFoo(Foo* self) { // Foo::Foo() self->vtable = &vtableFoo; // object points to class vtable } void copyConstructFoo(Foo* self, Foo* other) { // Foo::Foo(const Foo&) self->vtable = &vtableFoo; // don't copy from other! }
derived class Bar
typedef struct Bar_t { // class Bar Foo base; // inherit all members of Foo } Bar; void destructBar(Bar* self) { } // Bar::~Bar int aBar(Bar* self) { return 2; } // Bar::a() const struct slotsFoo vtableBar = { // one more constant table &vtableFoo, // can dynamic_cast to Foo (void(*)(Foo*)) destructBar, // must cast type to avoid errors (int(*)(Foo*)) aBar }; void constructBar(Bar* self) { // Bar::Bar() self->base.vtable = &vtableBar; // point to Bar vtable }
function f making a virtual function call
void f(Foo* arg) {
So you can see that vtable is just a static block in memory, mostly containing function pointers. Each polymorphic class object will point to a vtable corresponding to its dynamic type. It also simplifies the connection between RTTI and virtual functions: you can check what type of class is just by looking at what vtable points to. The above is simplified in many ways, for example, multiple inheritance, but the general concept sounds.
If arg is of type Foo* and you take arg->vtable , but actually an object of type Bar , then you will still get the correct vtable address. This is because vtable always the first element in the address of an object, regardless of whether it called vtable or base.vtable in a correctly typed expression.