The simplest way to explain it is probably this:
Virtual does some lookup for you, by adding a virtual lookup table.
In other words, if you didn't have the virtual keyword, and overrode a method, you would still have to call that method manually [forgive me if my memory for C++ syntax is a little rusty in spots]:
class A { void doSomething() { cout << "1"; } }
class B: public A { void doSomething() { cout << "2"; } }
class C: public A { void doSomething() { cout << "3"; } }
void someOtherFunc(A* thing) {
if (typeid(thing) == typeid(B)) {
static_cast<B*>(thing)->doSomething();
} else if (typeid(thing) == typeid(C)) {
static_cast<C*>(thing)->doSomething();
} else {
// not a derived class -- just call A's method
thing->doSomething();
}
}
You could optimise this a little (for readability AND performance, most likely), using a lookup table:
typedef doSomethingWithAnA(A::*doSomethingPtr)();
map<type_info, doSomethingWithAnA> A_doSomethingVTable;
void someOtherFuncA* thing) {
doSomethingWithAnA methodToCall = A_doSomethingVTable[typeid(thing)];
thing->(*methodToCall)();
}
Now, that's more of a high-level approach. The C++ compiler can obviously optimise this a bit more, by knowing exactly what "type_info" is, and so on. So probably, instead of that "map" and the lookup "methodToCall = aDoSomethingVTable[typeid(thing)]", then call, ", the compiler is inserting something much smaller and faster, like "doSomethingWithAnA* A_doSomethingVTable;" followed by "A_doSomethingTablething->type_number".
So you're right that C++ doesn't really NEED virtual, but it does add a lot of syntactic sugar to make your life easier, and can optimise it better too.
That said, I still think C++ is a horribly outdated language, with lots of unnecessary complications. Virtual, for example, could (and probably should) be assumed by default, and optimised out where unnecessary. A Scala-like "override" keyword would be much more useful than "virtual".