大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++理解inline的介入和排除

C++理解inline的介入和排除(1)

关键词:C++inline  阅读(882) 赞(11)

[摘要]本文主要是对C++理解inline的介入和排除的讲解,希望对您学习C++有所帮助!

inline 函数——多么棒的主意啊!它们看起来像函数,它们发作的效果也像函数,它们在各方面都比宏好得太多太多,而你却可以在调用它们时不招致函数调用的本钱。你还有什么更多的要求呢?

实际上你失掉的可以比你想的更多,由于避免函数调用的本钱只是故事的一部分。在典型情况下,编译器的优化是为了一段延续的没有函数调用的代码设计的,所以当你 inline 化一个函数,你可以就使得编译器可以对函数体实行上下文相关的特殊优化。大多数编译器都不会对 "outlined" 函数调用实行这样的优化。

但是,在编程中,就像在生活中,没有免费午餐,而 inline 函数也不例外。一个 inline 函数面前的思想是用函数本体替代每一处对这个函数的调用,而且不必拿着统计表中的 Ph.D. 就可以看出这样可以会添加你的目的代码的大小。在有限内存的机器上,过炽热衷于 inline 化会使得顺序关于可用空间来说过于庞大。即使运用了虚拟内存,inline 惹起的代码膨胀也会招致附加的分页调度,添加指令缓存命中率,以及随之而来的功用损失。

在另一方面,假定一个 inline 函数本体很短,为函数本体生成的代码可以比为一个函数调用生成的代码还要小。假定是这种情况,inline 化这个函数可以实际上招致更小的目的代码和更高的指令缓存命中率! 记住,inline 是向编译器发出的一个央求,而不是一个命令。这个央求可以以显式的或隐式的方式提出。隐式的方法就是在一个类定义的内部定义一个函数:

class Person {
 public:
  ...
  int age() const { return theAge; } // an implicit inline request: age is
  ... // defined in a class definition

 private:
  int theAge;
};

这样的函数通常是成员函数,不过我们知道友元函数也能被定义在类的内部,假定它们在那里,它们也被隐式地声明为 inline。

显式的声明一个 inline 函数的方法是在它的声明之前加上 inline 关键字。例如,以下就是标准 max 模板(来自 <algorithm>)经常用到的的完成方法:

template<typename T> // an explicit inline
inline const T& std::max(const T& a, const T& b) // request: std::max is
{ return a < b ? b : a; } // preceded by "inline"

max 是一个模板的理想引出一个观察结论:inline 函数和模板普通都是定义在头文件中的。这就使得一些顺序员得出结结论定函数模板必需是 inline。这个结论是合法的而且有潜在的危害,所以它值得我们调查一下。 inline 函数普通必需在头文件内,由于大多数构建环境在编译时期中止 inline 化。为了用被调用函数的函数本体交流一个函数调用,编译器必需知道函数看起来像什么样子。(有一些构建环境可以在衔接时期中止 inline 化,还有少数几个——比如,基于 .NET Common Language Infrastructure (CLI) 的控制环境——居然能在运转时 inline 化。但是,这些环境都是例外,并非规则。inline 化在大多数 C++ 顺序中是一个编译时行为。)

模板普通在头文件内,由于编译器需求知道一个模板看起来像什么以便用到它时对它中止实例化。(异常,也不是全部如此。一些构建环境可以在衔接时期中止模板实例化。但是,编译期实例化更为普遍。) 模板实例化与 inline 化有关。假定你写了一个模板,而且你以为一切从这个模板实例化出来的函数都应该是 inline 的,那么就声明这个模板为 inline,这就是上面的 std::max 的完成被做的事情。但是假定你为没有理由要 inline 化的函数写了一个模板,就要避免声明这个模板为 inline(无论显式的还是隐式的)。inline 化是有本钱的,而且你不希望在毫无预见的情况下遭遇它们。我们已经说到 inline 化是如何惹起代码膨胀的,但是,还有其它的本钱,过一会儿我们再讨论。

在做这件事之前,我们先来完成对这个结论的调查:inline 是一个编译器可以忽略的央求。大多数编译器拒绝它们以为太复杂的 inline 函数(例如,那些包括循环或许递归的),而且,除了最细碎的以外的全部虚拟函数的调用都不会被 inline 化。不应该对这后一个结论感到惊讶。虚拟意味着“等候,直到运转时才干断定哪一个函数被调用”,而 inline 意味着“执行之前,用被调用函数取代调用的地方”。假定编译器不知道哪一个函数将被调用,你很难责备它们拒绝 inline 化这个函数本体。

一切这些加在一同,得出:一个被指定的 inline 函数能否能真的被 inline 化,取决于你所运用的构建环境——主要是编译器。幸运的是,大多数编译器都有一个诊断层次,在它们不能 inline 化一个你提出的函数时,会招致一个警告。

有时分,即使当编译器完全何乐不为地 inline 化一个函数,他们还是会为这个 inline 函数生成函数本体。例如,假定你的顺序要持有一个 inline 函数的地址,编译器必需为它生成一个 outlined 函数本体。他们怎样能生成一个指向根本不存在的函数的指针呢?再加上,编译器普通不会对经过函数指针的调用中止 inline 化,这就意味着,对一个 inline 函数的调用可以被也可以不被 inline 化,依赖于这个调用是如何做成的:

inline void f() {...} // assume compilers are willing to inline calls to f

void (*pf)() = f; // pf points to f
...

f(); // this call will be inlined, because it’s a "normal" call
pf(); // this call probably won’t be, because it’s through
// a function pointer

甚至在你历来没有运用函数指针的时分,未 inline 化的 inline 函数的幽灵也会时不时地拜访你,由于顺序员并不必然是函数指针的独一需求者。有时分编译器会生成构造函数和析构函数的 out-of-line 拷贝,以便它们能失掉指向这些函数的指针,在对数组中的对象中止构造和析构时运用。

«上一页12下一页»


相关评论