C++如那里理惩罚内联虚函数
副标题#e#
当一个函数是内联和虚函数时,会产生代码替换或利用虚表挪用吗? 为了弄 清楚内联和虚函数,让我们将它们分隔来思量。凡是,一个内联函数是被展开的 。
class CFoo {
private:
int val;
public:
int GetVal() { return val; }
int SetVal(int v) { return val=v; }
};
这里,假如利用下列代码:
CFoo x;
x.SetVal(17);
int y = x.GetVal();
那么编译器发生的方针代码将与下面的代码段一样:
CFoo x;
x.val = 17;
int y = x.val;
你虽然不能这么做,因为val是个私有变量。内联函数的利益是不消函数挪用 就能埋没数据,仅此罢了。
虚函数有多态性,意味着派生的类能实现沟通的函数,但成果却差异。假设 GetVal 被声明为虚函数,而且你有第二个 以差异要领实现的类 CFoo2:
class CFoo2 : public CFoo {
public:
// virtual in base class too!
virtual int CFoo2::GetVal() {return someOtherVal;}
};
假如 pFoo是一个 CFoo 或 CFoo2 指针,那么,无论 pFoo 指向哪个类 CFoo 或 CFoo2,成员函数 pFoo->GetVal 都能挪用乐成。
假如一个函数既是虚拟函数,又是内联函数,会是什么环境呢?记着,有两 种方法成立内联函数,
第一种是在函数界说中利用要害字 inline,如:
inline CFoo::GetVal() { return val; }
第二种是在类的声明中编写函数体 ,就象前面的 CFoo2::GetVal 一样。所以假如将虚函数体包括在类的声明中, 如:
class CFoo {
public:
virtual int GetVal() { return val; }
};
#p#副标题#e#
编译器便认为这个函数 GetVal 是内联的,同时也是虚拟 的。那么,多态性和内联特性如何同时事情呢?
编译器遵循的第一个法则是无论产生什么工作,多态性必需起浸染。假如有 一个指向 CFoo 工具的指针,pFoo->GetVal 被担保去挪用正确的函数。一般 环境下,这就是说函数 GetVal 将被实例化为非内联函数,并有vtable(虚表) 进口指向它们。但这并不料味着这个函数不能被扩展!再看看下面的代码:
CFoo x;
x.SetVal(17)
int y = x.GetVal()
编译器知道x是 CFoo,而不是CFoo2,因为这个堆工具是被显式声明的。x必定 不会是CFoo2。所以展开 SetVal/GetVal 内联是安详的。假如要写更多的巨大代 码:
CFoo x;
CFoo* pfoo=&x;
pfoo->SetVal(17);
int y = pfoo->GetVal();
...
CFoo2 x2;
pfoo = &x2;
pfoo->SetVal(17); //etc.
编译器知道 pfoo 第一次指向x, 第二次指向x2,所以展开虚拟函数也是安详的。
你还可以编写更巨大的代码,个中,pfoo 所指的工具范例老是透明的,可是 大大都编译器不会做任何更多的阐明。纵然在前面的例子中,某些编译器将会安 全运行,实例化并通过一个虚表来挪用。实际上,编译器老是忽略内联需要并总 是利用虚表。独一绝对的法则是代码必需事情;也就是说,虚函数必需有多态行 为。
凡是,无论是显式照旧隐式内联,它只是一个提示罢了,并非是必需的,就 象寄存器一样。编译器完全能拒绝展开一个非虚内联函数,C++编译器经常首先 会报错:“内联间断-函数太大”。假如内联函数挪用自身,可能你 在某处通报其地点,编译器必需发生一个正常(外联?)函数。内联函数在 DEBUG BUILDS中不被展开,可配置编译选项来防范。
要想知道编译器正在做什么,独一的要领是看它发生的代码。对付微软的编 译器来说,你可以用-FA编译选项发生汇编清单。你不必知道汇编措施如何做。 我勉励你完成这个尝试;这对付相识呆板实际所做的工作呆板有益,同时你可学 习很多汇编列表中的内容。
有关内联函数的对象比你第一次打仗它时要巨大得多。有很多种环境强迫编 译器发生正常函数:递归,获取函数地点,太大的那些函数和虚函数。可是假如 编译器抉择实例化你的内联函数,就要思量把函数放在什么处所?它进入哪个模 块?
凡是类在头文件中声明,所以假如某个cpp包括foo.h,而且编译器抉择实例 化CFoo::GetVal,则在cpp文件中将它实例化成一个静态函数。假如十个模块包 含foo.h,编译器发生的虚函数拷贝就有十个。实际上,可以用虚表指向差异类 型的GetVal拷贝,从而是沟通范例的工具只发生拷贝。一些链接器能巧妙地在链 接时解除冗余,但一般你是不能指望他来担保的。
#p#分页标题#e#
我们得出的结论是:最好不要利用内联虚函数,因为它们险些不会被展开, 即便你的函数只有一行,你最好照旧将它与其它的类函数一起放在模块(cpp文 件)中。虽然,开拓者经常将简短的虚函数放在类声明中-不是因为他们但愿这 个函数被展开为内联,而是因为这样做更利便和可读性更强。