小众知识

首页 > 正文

如果virtual functions 被定义为inline,有何意义?

如题  


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 x2 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.

http://msdn.microsoft.com/msdnmag/issues/0600/c/

Today, I made a bug by using inline virtual functions. But I really didn't know it is a inline virtual function, as there is no virtual key word before the function declare of a member in .h file.  The function is a virtual function in the base class, but there isn't any virtual key word in the sub class. Gosh, whaterver it is! why not new and override as C sharp the super language?

Today, I made a bug by using inline virtual functions. And I sware I won't forget vitual anymore but must inline function.


2011-1-6
    除了发现有inline virtual外,今天还见到了__forceinline virtual。
    MSDN对inline的行为解释里有一条:“The function is virtual and is called virtually. Direct calls to virtual functions can be inlined.”。实验表明以上描述非常精确。
Release的情况下多态调用都会通过call指令,而无多态的直接调用在适合内联的情况下会被内联。表明被inline的virtual函数至少在编译后保留了一个非内联版本。
没有指明inline并且实现在cpp的函数,也可能被内联
virtual函数在适合的时候也会被内联
    可以看出,内联不内联由编译器说了算已经很靠谱了,inline关键字已经基本可以废掉了(头文件中的全局函数实现必须要inline关键字防止链接时报重复定义)。至于__forceinline这样一个force的关键字,除非非常确定,否则也没必要用,故弄玄虚。

 zengpan_panpan 发表于:2003-03-08 22:35:17
得分:1 

inline只是建议性的,这种情况编译器把它忽略就行了。 

 Frank001 发表于:2003-03-08 22:39:07
得分:1 


没有意义,即使这样定义 inline virtual void fn(){ …… }
在编译期仍被看作是非内联的。


 haihaihappysky 发表于:2003-03-10 11:41:54
得分:5 

我认为是有意义的,不过现代工程不建议这种用法的,不过virtual它的地址是在编译时固定下来的,这要从编译原理上讲,也许你不信我,不过这是真的,c++的语义分析是静态分析,不做动态分析,具体如何可谓是一言难尽的,这要你自己多深入学习了 

 shornmao 发表于:2003-03-10 13:11:13
得分:30 

從現在的C++編譯器的對象模型來看,虛函數一般是用虛函數表實現的,因此,在調用虛函數的時候,是從對象的虛函數表中查找虛函數的入口指針,然後調用。
因此a->foo()這樣的代碼,實際上被解釋成a->vptr_table["foo"](),因此在編譯時,並不能決定複製哪一個類的foo()成員函數,因爲它不知道a的實際對象,所以是無法inline的。
然而,在虛函數定義前要求inline,並不會產生錯誤,但是在實際的編譯器實現中,大多數情況下會導致可執行代碼增長,因爲很多編譯器在無法inline時,為每個translation unit中的函數調用都複製完全相同的代碼,而不是共享同一段代碼,這增加了編譯時間從而降低了開發效率。
但是,C++標準並沒有要求虛函數必須採用查表法實現。然而從工程的角度上說,應該避免inline虛函數。具體的請看C++代碼設計与重用。 

 smzh8 发表于:2003-03-10 17:38:21
得分:1 

定义在类体内的成员函数为内联函数,调用该函数不需要转向执行函数体,而是用函数体的代码替换,这样减少开销,提高效率。如果函数体定义在类外,为了表明是内联函数,所以在函数头加inline。 

 HaiFen 发表于:2003-03-10 20:34:23
得分:10 

如果某个调用能够解析为对某个类的调用,那这个调用就可能被在线化!
如:
class Base{
public:

virtual int vfun(){return 0}
};
class Derived
{
int vfun(){return 1;}
};

void test()
{
 Derived d;
int result = d.vfun();
}
这个调用就可以inline 化 

 shornmao 发表于:2003-03-11 09:36:41
得分:0 

to HaiFen(小石头) :
你的例子不錯,但是這個是嚴重依賴編譯器實現的行爲,你能夠肯定你的編譯器一定可以精巧的實現這個優化嗎?即掃描虛函數内部是否使用了this指針? 

 Solstice 发表于:2003-03-11 12:39:44
得分:1 

virtual 和 inline 可以用在一起,Greta Regular Expression library中大量使用这种技巧。

见:

http://www.cuj.com/articles/2003/0301/0301d/0301d.htm?topic=articles 

 xkak2 发表于:2003-03-11 18:53:13
得分:1 

没有任何意义,因为virtual意味着运行时才知道位置,如何inline? 

上一篇:谁有《设计模式》的电子版?
下一篇:内联函数引起的疑惑

分享到: