定义一个类。

 view plaincopy to clipboardprint?
class CA   
{   
public:   
    void FunA()   
    {   
    };   
};  
class CA
{
public:
  void FunA()
  {
  };
}; 

通过指针调用FunA。

view plaincopy to clipboardprint?
CA* pa = new CA;   
pa->FunA();  
  CA* pa = new CA;
  pa->FunA(); 

此时,转入汇编,我们看看pa->FunA()到底是怎么做的。

pa->FunA();

// 汇编展开如下:

004113E7  mov         ecx,dword ptr [pa] 

// 函数调用。
004113EA  call        CA::FunA (411005h) 
// 看看FunA做了什么。中间做了一个跳转

00411005  jmp         CA::FunA (411420h) 

// 继续展开。

 void FunA()
 {

// EBP压栈,为函数调用保护现场。
00411420  push        ebp  
00411421  mov         ebp,esp 
00411423  sub         esp,0CCh 
00411429  push        ebx  
0041142A  push        esi  
0041142B  push        edi  
0041142C  push        ecx  

//  函数FunA并没有任何操作,为什么汇编展开成这么多,我暂时也不知道为什么。
0041142D  lea         edi,[ebp-0CCh] 
00411433  mov         ecx,33h 
00411438  mov         eax,0CCCCCCCCh 
0041143D  rep stos    dword ptr es:[edi] 
0041143F  pop         ecx  
00411440  mov         dword ptr [ebp-8],ecx 
 };

// 函数返回
00411443  pop         edi  
00411444  pop         esi  
00411445  pop         ebx  
00411446  mov         esp,ebp 
00411448  pop         ebp  
00411449  ret            

至此。函数调用完成。

可是如果我们为FunA声明为virtual,会发生什么呢?

view plaincopy to clipboardprint?
class CA   
{   
public:   
    virtual void FunA()   
    {   
    };   
};  
class CA
{
public:
  virtual void FunA()
  {
  };
};

// 看看汇编吧。

 pa->FunA();

// 调用咱开。
0041146D  mov         eax,dword ptr [pa] 
00411470  mov         edx,dword ptr [eax] 
00411472  mov         esi,esp 
00411474  mov         ecx,dword ptr [pa] 

// 将虚函数的地址,从虚表中取出来,放入到EAX
00411477  mov         eax,dword ptr [edx] 

// 调用虚函数。下面这一句才是真铮的调用FunA。

// 前面的调用均是准备工作,

// 目的是将FunA在CA对象的虚表指针指向的虚表中的地址取出来。
00411479  call        eax  

// 安全性检查,由于是Debug下运行,如下汇编会在release会优化
0041147B  cmp         esi,esp 
0041147D  call        @ILT+355(__RTC_CheckEsp) (411168h) 

对00411479  call        eax  这一句汇编展开。

CA::FunA:
00411005  jmp         CA::FunA (411510h) 

 virtual void FunA()
 {
00411510  push        ebp  
00411511  mov         ebp,esp 
00411513  sub         esp,0CCh 
00411519  push        ebx  
0041151A  push        esi  
0041151B  push        edi  
0041151C  push        ecx  
0041151D  lea         edi,[ebp-0CCh] 
00411523  mov         ecx,33h 
00411528  mov         eax,0CCCCCCCCh 
0041152D  rep stos    dword ptr es:[edi] 
0041152F  pop         ecx  
00411530  mov         dword ptr [ebp-8],ecx 
 };
00411533  pop         edi  
00411534  pop         esi  
00411535  pop         ebx  
00411536  mov         esp,ebp 
00411538  pop         ebp  
00411539  ret         

后面的进入到函数FunA内部,与非virutual的函数调用汇编一致。

上述汇编均在Debug模式下展开。

可以发现,使用virtual的主要效率损失是在函数调用的准备阶段,

我们需要在对象的虚表指针的某一个位置取出函数实际地址,继而转入函数地址进行函数调用。

在Release模式下,我们看看函数调用的准备阶段到底发生了什么。

不使用virtual:

在本机上FunA的调用直接被优化掉了。断点无法命中。

于是我觉得需要给他加一些有意义的代码,我们只看函数调用准备阶段到底做了什么。

pa->FunA();

// 展开时就是一个call指令,直接指向函数地址。
0040100A  call        dword ptr [__imp__rand (4020A4h)] 

使用virtual:

 pa->FunA();
00401024  mov         edx,dword ptr [eax] 
00401026  mov         ecx,eax 
00401028  mov         eax,dword ptr [edx] 
0040102A  call        eax  

// 对call调用展开,发现该函数就是一个ret指令。因为该函数并没有任何有意义的执行语句。

 virtual void FunA()
 {
 };
00401000  ret              
--- No source file -------------------------------------------------------------
00401001  int         3    
00401002  int         3    
00401003  int         3    
00401004  int         3    
00401005  int         3    
00401006  int         3    
00401007  int         3    
00401008  int         3    
00401009  int         3    
0040100A  int         3    
0040100B  int         3    
0040100C  int         3    
0040100D  int         3    
0040100E  int         3    
0040100F  int         3    
--- e:\workspace\virtualdemo\virtualdemo\virtualdemo.cpp -----------------------
};

结论:

不使用virtual,一个成员函数的调用只需要一条call指令。

如果使用virtual,一个成员函数调用,需要额外的三条mov指令来

呵呵,当我们在享用virtual带来的多态时,

也带来了一些效率上的损失。

同时virtual也会抑制inline以及调用优化。

对于不必要的virtual还是不要为好。

个人意见,欢迎拍砖。