首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

询问 inline 关键字的用法?解决方法

2012-04-01 
询问 inline 关键字的用法?1.inline用在函数声明时,还是函数定义时?还是两边都加?2.inline的函数可不可以

询问 inline 关键字的用法?
1.inline用在函数声明时,还是函数定义时?还是两边都加?

2.inline的函数可不可以同时是虚函数?

3.构造函数可不可以加inline关键字,我发现加后,编译没问题,如果在其他文件中调用,链接时会出现错误!

4.如果构造函数在类的外部定义,且不加inline关键字,跟在类的内部定义有没有区别?



[解决办法]
看看effective c++中的建议:
条款33: 明智地使用内联

内联函数------多妙的主意啊!它们看起来象函数,运作起来象函数,比宏(macro)要好得多(参见条款1),使用时还不需要承担函数调用的开销。你还能对它们要求更多吗?

然而,你从它们得到的确实比你想象的要多,因为避免函数调用的开销仅仅是问题的一个方面。为了处理那些没有函数调用的代码,编译器优化程序本身进行了专门的设计。所以当内联一个函数时,编译器可以对函数体执行特定环境下的优化工作。这样的优化对 "正常 "的函数调用是不可能的。

我们还是不要扯得太远。程序世界和现实生活一样,从来就没有免费的午餐,内联函数也不例外。内联函数的基本思想在于将每个函数调用以它的代码体来替换。用不着统计专家出面就可以看出,这种做法很可能会增加整个目标代码的体积。在一台内存有限的计算机里,过分地使用内联所产生的程序会因为有太大的体积而导致可用空间不够。即使可以使用虚拟内存,内联造成的代码膨胀也可能会导致不合理的页面调度行为(系统颠簸),这将使你的程序运行慢得象在爬。(当然,它也为磁盘控制器提供了一个极好的锻炼方式:))过多的内联还会降低指令高速缓存的命中率,从而使取指令的速度降低,因为从主存取指令当然比从缓存要慢。

另一方面,如果内联函数体非常短,编译器为这个函数体生成的代码就会真的比为函数调用生成的代码要小许多。如果是这种情况,内联这个函数将会确实带来更小的目标代码和更高的缓存命中率!

要牢记在心的一条是,inline指令就象register,它只是对编译器的一种提示,而不是命令。也就是说,只要编译器愿意,它就可以随意地忽略掉你的指令,事实上编译器常常会这么做。例如,大多数编译器拒绝内联 "复杂 "的函数(例如,包含循环和递归的函数);还有,即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。(这一点也不奇怪。virtual的意思是 "等到运行时再决定调用哪个函数 ",inline的意思是 "在编译期间将调用之处用被调函数来代替 ",如果编译器甚至还不知道哪个函数将被调用,当然就不能责怪它拒绝生成内联调用了)。以上可以归结为:一个给定的内联函数是否真的被内联取决于所用的编译器的具体实现。幸运的是,大多数编译器都可以设置诊断级,当声明为内联的函数实际上没有被内联时,编译器就会为你发出警告信息(参见条款48)。

假设写了某个函数f并声明为inline,如果出于什么原因,编译器决定不对它内联,那将会发生些什么呢?最明显的一个回答是将f作为一个非内联函数来处理:为f生成代码时就象它是一个普通的 "外联 "函数一样, 对f的调用也象对普通函数调用那样进行。

理论上来说确实应该这样发生,但理论和现实往往会偏离,现在就属于这种情况。因为,这个方案对解决 "被外联的内联 "(outlined inline)这一问题确实非常理想,但它加入到C++标准中的时间相对较晚。较早的C++规范(比如ARM------参见条款50)告诉编译器制造商去实现的是另外不同的行为,而且这一旧的行为在现在的编译器中还很普遍,所以必须理解它是怎么一回事。

稍微想一想你就可以记起,内联函数的定义实际上都是放在头文件中。这使得多个要编译的单元(源文件)可以包含同一个头文件,共享头文件内定义的内联函数所带来的益处。下面给出了一个例子,例子中的源文件名以常规的 ".cpp "结尾,这应该是C++世界最普遍的命名习惯了:


// 文件example.h
inline void f() { ... } // f的定义

...

// 文件source1.cpp
#include "example.h " // 包含f的定义

... // 包含对f的调用

// 文件source2.cpp
#include "example.h " // 也包含f的定义

... // 也调用f
假设现在采用旧的 "被外联的内联 "规则,而且假设f没有被内联,那么,当source1.cpp被编译时,生成的目标文件中将包含一个称为f的函数,就象f没有被声明为inline一样。同样地,当source2.cpp被编译时,产生的目标文件也将包含一个称为f的函数。当想把两个目标文件链接在一起时,编译器会因为程序中有两个f的定义而报错。

为了防止这一问题,旧规则规定,对于未被内联的内联函数,编译器把它当成被声明为static那样处理,即,使它局限于当前被编译的文件。具体到刚才看到的例子中,遵循旧规则的编译器处理source1.cpp中的f时,就象f在source1.cpp中是静态的一样;处理source2.cpp中的f时,也把它当成在source2.cpp中是静态的一样。这一策略消除了链接时的错误,但带来了开销:每个包含f的定义(以及调用f)的被编译单元都包含自己的f的静态拷贝。如果f自身定义了局部静态变量,那么,每个f的拷贝都有此局部变量的一份拷贝,这必然会让程序员大吃一惊,因为一般来说,函数中的 "static "意味着 "只有一份拷贝 "。

具体实现起来也会令人吃惊。无论新规则还是旧规则,如果内联函数没被内联,每个调用内联函数的地方还是得承担函数调用的开销;如果是旧规则,还得忍受代码体积的增加,因为每个包含(或调用) f的被编译单元都有一份f的代码及其静态变量的拷贝!(更糟糕的是,每个f的拷贝以及每个f的静态变量的拷贝往往处于不同的虚拟内存页面,所以两个对f的不同拷贝进行调用有可能导致多个页面错误。)

还有呢!有时,可怜的随时准备为您效劳的编译器即使很想内联一个函数,却不得不为这个内联函数生成一个函数体。特别是,如果程序中要取一个内联函数的地址,编译器就必须为此生成一个函数体。编译器怎么能产生一个指向不存在的函数的指针呢?


inline void f() {...} // 同上
void (*pf)() = f; // pf指向f
int main()
{
f(); // 对f的内联调用
pf(); // 通过pf对f的非内联调用
...
}
这种情况似乎很荒谬:f的调用被内联了,但在旧的规则下,每个取f地址的被编译单元还是各自生成了此函数的静态拷贝。(新规则下,不管涉及的被编译单元有多少,将只生成唯一一个f的外部拷贝)

即使你从来不使用函数指针,这类 "没被内联的内联函数 "也会找上你的门,因为不只是程序员会使用函数指针,有时编译器也这么做。特别是,编译器有时会生成构造函数和析构函数的外部拷贝,这样就可以通过得到那些函数的指针,方便地构造和析构类的对象数组(参见条款M8)。


[解决办法]
1.inline用在函数声明时,还是函数定义时?还是两边都加?
首先,内联函数声明和定义最好在同一个文件中,其它的情况没有实用上的意义。
只要在同一个文件中,声明和定义至少其一加“inline”修饰即可。

2.inline的函数可不可以同时是虚函数?
可以。
因为inline只是给编译器的一种建议,编译器并不保证一定进行内联展开,也并不保证一定不进行内联展开。更详细地讲,假如有10个地方调用了这个函数,并不保证10处都会内联展开或都不会内联展开,完全可能实际上只展开了5处。另外5处没有展开。


因此,virtual函数作inline也还是有意义的,只要编译器发现某一处是静态调用(比如从对象上而非从指针或引用上),则至少这一处调用还是完全有可能做内联展开的。

3.构造函数可不可以加inline关键字,我发现加后,编译没问题,如果在其他文件中调用,链接时会出现错误!
那说明你的构造函数的定义和声明不在同一个文件中。
这种情况别去研究了,本来很简单的问题,只是一两句说不清楚,而且也没有太多实际意义。
内联函数要么直接用定义做声明,要么让声明和定义在一处(一般是头文件中)

4.如果构造函数在类的外部定义,且不加inline关键字,跟在类的内部定义有没有区别?
没有区别,只要跟类的定义在同一个头文件中。
[解决办法]
1 都加inline关键字
2 virtual 函数使用 inline, 语法上可以,但是实际上,这种用法 inline 是完全无效的
3、4 构造函数最好使用 inline 并将声明/实现放在一起~
[解决办法]
1.inline用在函数声明时,还是函数定义时?还是两边都加?
为什么说内联函数定义和声明要在同一个文件中呢,因为内联函数的实现是以一个编译单位为边界的,也就是说如果你在一个头文件中定义了内联函数,在另一个CPP文件中给出了它的实现,然后想通过包含头文件去调用内联函数是行不通的,因为内联函数的实现仅仅在定义它的文件中可见。

2.inline的函数可不可以同时是虚函数?
这个不保证说的全对:本质上inline与虚函数是两个完全不同的概念,一个编译时代码替换,一个是运行时判断,所以我坚决反对这两个东西同时用。当你觉得需要这样用的时候,实际上是你的语义出了问题,首先想的应该是如何避免这样的需要。 任何时候都不要利用语言的功能去支持一个恶劣的语义。

3.构造函数可不可以加inline关键字,我发现加后,编译没问题,如果在其他文件中调用,链接时会出现错误!
请看第一条,内联函数的实现仅仅是编译单位内可见的。

4.如果构造函数在类的外部定义,且不加inline关键字,跟在类的内部定义有没有区别?
不大确定,呵呵
[解决办法]
偶还是觉得虚函数inline并非无效,更不是“恶劣”。

没错,虚函数往往意味着“动态”的东西,至少给人的感觉是这样。但它的这种动态性却本来就不是必定的,只有从引用或指针上调用它时,这种动态性才是必定的。

而从一般对象上调用它时,它跟一般函数没有区别,所以完全可以声明为内联,也完全可能被内联展开。

热点排行