The main-loop is probably the best place for virtual functions - they only get called once a frame then. Where you run into trouble, is when they need to be called many times per frame.
On modern hardware with modern compilers, there''s no significant performance difference between a function call and a virtual call. As Fruny mentioned it''s one table look-up, which (would be) nominally be 1 (pipelined) CPU tick (being read from the L1 cache) - however all function calls cause a pipe-line stall as they are branching instructions. The cost of the function call setup, return, and branch significantly outweigh the cost of the look-up iff the look-up does not cause any type of cache-miss. If it''s a soft L1 miss (found in L2 or L3 cache) the cost of the call setup and the look-up would be closer, and if it''s hard cache-miss (it has to actually read memory) then the look-up may exceed the call setup. Note that the function call itself can cause a code cache miss, and this is far more likely when using function pointers (e.g. virtual functions) then when calling ''normal'' functions.
If the functions do a minimal amount of work, performance can be greatly enhanced by inlining them. It''s not feasible to inline virtual functions (or a call made by function pointer), so using virtual methods for functions that perform little work is not advisable if they will be called often.
Consider this pathological example:
//Case 1interface IPureOO { virtual ~IPureOO()=0{} virtual int GetID()=0; };struct CImplOO : IPureOO { ~CImplOO(){} virtual int GetID(){return 1;} };//Case 2struct NotSoPureOO { NotSoPureOO(int id) : ID(id) {} const int ID; int GetID(){return this->ID;} };struct ImplNotSoPure : NotSoPureOO { ImplNotSoPure() : NotSoPureOO(1) {} }
The performance difference is astounding, though both implementations exhibit the same behavior. The difference is that second case takes advantage of the knowledge that every sub-class will implement GetID using an integer. Technically you lose flexibility in the second case, but it''s flexibility that will never be used.