Inline Virtual Functions

From: http://msdn.microsoft.com/zh-cn/magazine/cc301407(en-us).aspx

Q
How does C++ handle inline virtual functions? When a function is inline and virtual, will code substitution take place or is the call resolved using the vtable? 

G.N. Rajagopal
A
The answer is, it depends. To see why, let's consider each caseâ€"inline and virtualâ€"separately. Normally, an inline function is expanded, well, inline.


class CFoo {
private:
    int val;
public:
    int GetVal()      { return val; }
    int SetVal(int v) { return val=v; }
}; 
In this situation, if you write


CFoo x; 
x.SetVal(17); 
int y = x.GetVal(); 
then the compiler generates the code as if you'd written:


CFoo x; 
x.val = 17; 
int y = x.val; 
Of course you can't do this because val is private. Inline functions give you the advantage of data hiding without paying the price of a function call. So much for inline functions.
      Virtual functions give you polymorphism, which means derived classes can implement the same function differently. Suppose GetVal is now declared virtual and you have a second class, CFoo2, with a different implementation:


class CFoo2 : public CFoo { 
public: 
    // virtual in base class too!
    virtual int CFoo2::GetVal() { 
        return someOtherVal; 
    } 
}; 
If pFoo is a pointer to a CFoo or CFoo2, then pFoo->GetVal will call the member function for whichever class pFoo really points toâ€"CFoo or CFoo2. This is C++ 101, and you should know it like the back of your hand. 
      But what happens if a function is both virtual and inline? Remember, there are two ways to make a function inline: by using the inline keyword in the function definition, as in


inline CFoo::GetVal() { return val; } 
or by coding the function body inline within the class declaration, as in the previous CFoo2::GetVal example. So if you include the body of your virtual function within the class declaration


class CFoo { 
public:
    virtual int GetVal() { return val; } 
}; 
then you are telling the compiler you want GetVal to be both inline  and  virtual. This doesn't seem to make sense, for how can polymorphism work if the function is expanded inline? The answer, of course, is that it can't. Or can it? 
      The first rule the compiler follows when it encounters such a beast is this: whatever happens, polymorphism must work. If you have a pointer to a CFoo object, then pFoo->GetVal is guaranteed to call the right function. In general, this means the GetVal functions will be instantiated as real (noninline) functions, with vtable entries pointing to them. But that doesn't mean the function can't ever be expanded! Consider this code again:


CFoo x; 
x.SetVal(17); 
int y = x.GetVal(); 
      The compiler knows that  x  is really a CFoo, not a CFoo2, since the stack object is explicitly declared. There's no way  x  could really be a CFoo2. So it's safe to expand SetVal/GetVal inline. If you write this more complex code


CFoo x; 
CFoo* pfoo=&x; 
pfoo->SetVal(17); 
int y = pfoo->GetVal(); 
���
CFoo2 x2; 
pfoo = &x2; 
pfoo->SetVal(17); //etc. 
the compiler knows that pfoo points to  x  the first time and  x 2 the second time, so again it's safe to expand the virtual functions. You can dream up ever more complex examples, where the type of object pfoo points to is always known, but most compilers won't do any heavy analysis. Even in the preceding example, some compilers will do the safe thing, which is to instantiate the function and call through a vtable. Indeed, the compiler is free to ignore the inline requirement and  always  use the vtable. The only absolute rule is that the code must work; that is, virtual functions must behave polymorphically. 
      In general, inlineâ€"whether explicit or implicitâ€"is a hint, not a mandate, just like register. (Does anyone remember register? It asks the compiler to use a machine register for a variable if it can.) The compiler can refuse to expand even a nonvirtual inline function if it wants to, and the first C++ compilers often complained, "inline abortedâ€"function too big." Certainly if an inline function calls itself, or if you pass its address somewhere, the compiler must generate a normal (outline?) function. And, of course, inline functions are not expanded in debug builds, or if you set a compiler option preventing it. 
      The only way to really know what your compiler is doing is to look at the code it generates. For the Microsoft® compiler, you can compile with -FA to generate an assembly listing. You don't need to be an assembler jock to know what's going on. I encourage you to perform this experiment; it's good for the soul to see what the machine is actually doing, and you can learn a lot poking around assembly listings. For now, I'll spare you that agony. 
      The topic of inline functions is more complex than you might think at first. There are many circumstances that force the compiler to generate a normal function: recursion, taking the address of your function, functions that are too big, and virtual functions. Here's another consideration: if the compiler decides to instantiate your inline function, where does it put the function? Which module does it go into? 
      Usually, classes are declared in header (.h) files. So if mumble.cpp includes foo.h and the compiler decides it has to instantiate CFoo:: GetVal, it will instantiate it as a static function in mumble.cpp. If 10 modules include foo.h, the compiler could generate up to 10 copies of your virtual function. In fact, you could end up with objects of the same type with vtables pointing to different copies of GetVal. Yuk! Some linkers are smart enough to eliminate the redundancies at link time, but in general you can't be sure. 
      So the bottom line is: it's best not to use inline virtual functions, since they're almost never expanded anyway. Even if your function is just one line, you're better off putting it in the module (.cpp file) along with the other class functions. Of course, programmers often put short virtual functions in the class declarationâ€"not because they expect the function to be expanded inline, but because it's more convenient and readable. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
inline virtual 是一种面试问题,涉及到C++中的虚函数和内联函数的组合使用。 在C++中,虚函数需要通过虚函数表进行调用,在运行时根据对象的动态类型进行分派,因此会带来一定的运行时开销。而内联函数则是在每个调用点直接将函数体插入到调用位置,以减少函数调用的开销。 当我们将虚函数声明为内联函数时,编译器会尝试将虚函数内联展开,即将函数体直接插入到调用点,以减少虚函数调用的开销。然而,内联函数需要在编译时进行展开,而虚函数需要在运行时进行动态分派,两者的特性存在冲突。 实际上,虚函数不能被声明为内联函数。这是因为内联函数在编译时就要展开,而虚函数的具体实现在运行时根据对象的动态类型决定,无法在编译时确定。因此,将虚函数声明为内联函数是无效的,编译器会忽略内联修饰符。 然而,即使虚函数不能被声明为内联函数,我们仍然可以通过将其定义放在类定义体内部,以便于编译器对虚函数进行内联展开的优化。这样做会在一定程度上降低虚函数调用带来的运行时开销,但并不等同于将虚函数声明为内联函数。 总结而言,inline virtual 不是一个合法的C++语法,虚函数无法被声明为内联函数。虽然我们可以通过将虚函数的定义放在类定义体内部来提供内联展开的优化,但这并不等同于将虚函数声明为内联函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值