Link to home
Start Free TrialLog in
Avatar of ronen_di
ronen_di

asked on

inline virtual function

Can a virtual function be also inline ?
please explain the issue to me .
Avatar of Iexpert
Iexpert

No,
Because the way virtual functions are implemented in C++
is that an array of function pointers (the vtable) is automatically
built for each class. Inline functions are resolved at compile
time by the compiler and hence the call through a function
pointer can't be used.
You can declare a virtual function inline the same as you can any other C++ function.  However the compiler is almost never likely to take any notice of the request (inline is a hint to the compiler, nothing more, so the compiler is free to ignore it).

The problem is that virtual function calls are not resolved until run-time.  That is the code to be executed cannot be determined until run time, and hence the compiler cannot inline the code to be called.  

Remember that virtual methods may not even be recompiled when another derived class is added, so any 'inlining' of this sort would have to be left to link time - so effectively you would need a 'smart' linker.  I am not aware of any that can do this.
virtual functions -> run time resolved
inline -> compile time 'injected' code
=> Nope, you can't 'inline' virtual functions
As an example consider the following:

file Base.h:

class Base
{
public:
   virtual void f() = 0;
}

class Derived : public Base
{
public:
   virtual void f() {
       // Do Something
   }
};

void processSet(vector<Base*>& bvec);

and in Base.cpp

#include "Base.h"

void processSet(vector<Base*>& bvec)
{
    for (int i=0; i<bvec.size(); i++)
         bvec[i]->f();
}

and main.cpp

#include "Base.h"

void main()
{
    vector<Base*> bvec;
    bvec.push_back(new Derived);
    processSet(bvec);
}

In this situation you could argue that the call to bvec[i]->f() could be inlined since there is only one possible expansion.  However the compiler *cannot* do this because things may change without Base.cpp being recompiled.  So, if you changed main.cpp to:

#include "Base.h"

class MyDerived : public Base
{
public:
   virtual void f() {
      // Do Something Else
   }
};

void main()
{
    vector<Base*> bvec;
    bvec.push_back(new Derived);
    bvec.push_back(new MyDerived);
    processSet(bvec);
}

you would expect everything to work without recompiling Base.cpp.  So the compiler could not have done the inline expansion when it compiled Base.cpp.  The linker could -conceivably- have made this decision,  but I think that sort of behaviour is way beyond the sort of thing that most linkers could do.

ASKER CERTIFIED SOLUTION
Avatar of abdij
abdij

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
It depends on implementation of the compiler, for instance:

struct A
{
  virtual void f() { cout << "A::f()"; }
};

struct B : A
{
  virtual void f() { cout << "B::f()"; }
};

void test()
{
  B b;
  A *a = &b;
  b.f();   // resolved at compile time, code is emitted directly (inline!)
  a->f(); // resolved at run time, VTABLE entry call is used
};

So inline virtual CAN make sense, it depends on compiler implementation. Notice, that instead of jasonclarckes example, this is a clear situation for the compiler, b.f() can always be resolved at compiletime.
BTW I tested it with VC6, and this is exactly what happens, b.f() is indeed emitted inline, while for a->f() the VTABLE entry is called.
Luc
How does VC respond to the following scenario (can't test myself, gcc ignores the inline request in this case =)

// inline.h ==========================
#include <stdio.h>

struct A
{
   virtual void f();
   // other stuff  
};


void g1();
void g2();
//=======================================================
// inline1.cpp =======================
#include "inline.h"

inline void A::f()
{puts("inline variant");}
void g1()
{
   A a;
   a.f();
}
//=======================================================
// inline2.cpp =======================
#include "inline.h"

//void A::f()
//{puts("virtual variant");}

void g2()
{
   A a;
   a.f();
}
//=======================================================
// inline.cpp ========================
#include "inline.h"

int main()
{
   g1();
   g2();
}
//=======================================================
You mean the following scenario (your example could be clearer KangaRoo):
struct A
{
  virtual void f() { cout << "A::f()"; } // inline
};

struct B : A
{
  virtual void f(); // outline
};
void B::f() { cout << "B::f()"; }

void test()
{
  A a; a.f();
  B b; b.f();
};
In test the code for A::f() is emitted inline. the function B::f() is called directly, so the VTABLE entry is not used.
Conclusion, if VC6 can resolve the function at compile-time it uses a direct function call or emits the code directly inline, only if the function cannot be resolved at compile time it uses the VTABLE entry.
In fact I like that, good piece of optimization here...
Not that it is really important, the execution-time overhead of late binding is not that great,  just an extra mov to load the VTABLE into a register.
Luc
You wouldn't be a good tester :-)) Off course that will work.
My example is an attempt to trip up VC's inlining strategie.
Same thing happens again: in g1() the code is emitted inline, in g2() the function is called directly.
(I asume ofcourse that A::f shouldn't be commented out in inline2.cpp, otherwise it won't compile)
Oeps. yes.
This means two different 'versions' of A::f are generated. Doesn't that sound like a potential source for trouble?
Yes it does, but that goes for all compilers.... this is not specific VC.
I mean, f() does not need to be virtual to let this construction cause a potential bug, if you declare a prototype, and in one source file define the function, and in the other define it as an inline, no compiler will complain, and the linker won't notice it, because the inline is not linked.
In any case, C++ doesn't protect you from being stupid...
Luc
I admit, it is far fetched...
> Not that it is really important, the
> execution-time overhead of late
> binding is not that great,  just an
> extra mov to load the VTABLE into a
register.

right the overhead of a virtual function call as compared to a normal function call is usually insignificant.  But the overhead of a function call compared to no function call can be significant, which is why you have to take care in performance critical code if you have lots of 'small' virtual methods...

> This means two different 'versions' of A::f are generated.

compilers certainly used to behave that way, if an in-line function could not be 'in-lined' then one function would likely be generated for each compilation unit.  However, I think the standard now says that if a function is not 'in-lined' there will be only one definition - so I am not sure how VC++ can deal with this requirement behaving as you describe/