C ++中的内联成员函数

时间:2009-03-02 18:16:49

标签: c++ inline-method

ISO C ++表示C ++中成员函数的内联定义与使用内联声明它相同。这意味着将在使用成员函数的每个编译单元中定义该函数。但是,如果函数调用因任何原因无法内联,则该函数将“照常”实例化。 (http://msdn.microsoft.com/en-us/library/z8y1yy88%28VS.71%29.aspx)我对这个定义的问题是它没有告诉它将被实例化到哪个翻译单元。 我遇到的问题是,当在一个静态库中面对两个目标文件时,这两个目标文件都引用了一些无法内联的内联成员函数,链接器可能会“选择”任意对象文件作为定义的源。这种特殊选择可能会引入不必要的依赖关系。 (除其他外)

例如: 在静态库中

A.h:

class A{
  public:
    virtual bool foo() { return true; }
};

U1.cpp:

A a1;

U2.cpp:

A a2;

以及许多依赖项

在另一个项目中 main.cpp中:

#include "A.h"

int main(){
  A a;
  a.foo();
  return 0;
}

第二个项目是第一个。我如何知道编译器将使用哪个定义,以及哪些目标文件与其依赖关系将被链接?那个标准上有什么标准吗? (试过,但未能找到)

由于

编辑:因为我看到有些人误解了问题所在,我想强调:如果编译器决定为该函数创建一个符号(在这种情况下,它会,因为'虚拟',在不同的目标文件中会有几个(外部看到的)实例化,链接器会选择哪个定义(从哪个目标文件?)?)

5 个答案:

答案 0 :(得分:8)

只是我的两分钱。这不是特别关于虚函数,而是关于内联函数和成员函数。也许它很有用。

C ++

就标准C ++而言,必须在使用它的每个翻译单元中定义内联函数 。并且非静态内联函数将在每个转换单元和相同的地址中具有相同的静态变量。编译器/链接器必须将多个定义合并到一个函数中才能实现此目的。因此,总是将内联函数的定义放入标题中 - 或者如果仅在实现文件(“.cpp”)中定义它(对于非成员函数),则不将其声明放入标题中,因为如果你会有人使用它,你会得到一个关于未定义函数或类似东西的链接器错误。

这与非内联函数不同,非内联函数必须在整个程序中仅定义一次( one-definition-rule )。对于内联函数,如上所述的多个定义是正常情况。并且这与呼叫是否是内部内联无关。关于内联函数的规则仍然很重要。 Microsoft编译器是否遵守这些规则 - 我无法告诉您。如果它在这方面符合标准,那么它就会。但是,我可以想象使用虚拟,dll和不同TU的某些组合可能会有问题。我从未测试过,但我相信没有问题。

对于成员函数,如果在类中定义函数,则它是隐式内联的。并且因为它出现在标题中,所以必须在使用它的每个翻译单元中定义它的规则被自动满足。但是,如果您在类外定义函数并在头文件中定义(例如,因为存在循环依赖关系,其中包含代码),如果您多次包含相应的文件,那么该定义必须是内联的,避免链接器抛出的多重定义错误。文件示例f.h

struct f {
    // inline required here or before the definition below
    inline void g();
};

void f::g() { ... }

这与将定义直接放入类定义具有相同的效果。

C99

请注意,有关内联函数的规则对于C99而言比对C ++更复杂。这里,内联函数可以定义为内联定义,其在整个程序中可以存在多个。但是如果使用这样的(内联)定义(例如,如果它被调用),那么必须在另一个翻译中包含的整个程序中也正好一个外部定义单元。基本原理(引用PDF解释几个C99功能背后的基本原理):

  

C99中的内联确实以两种方式扩展了C ++规范。首先,如果函数在一个翻译单元中内联声明,则不需要在每个其他翻译单元中内联声明。例如,这允许库函数在库中内联,但仅通过其他地方的外部定义可用。对外部函数使用包装函数的替代方法需要一个额外的名称;如果翻译者实际上没有进行内联替换,它也可能会对性能产生负面影响。

     

其次,要求内联函数的所有定义都“完全相同”的要求被程序行为不应取决于是否使用可见内联定义或外部定义实现调用的要求所取代,一个功能。这允许内联定义专门用于在特定翻译单元内使用。例如,库函数的外部定义可能包括一些参数验证,这些验证对于从同一库中的其他函数进行的调用不需要。这些扩展确实提供了一些优势;关注兼容性的程序员可以简单地遵守更严格的C ++规则。

为什么我在这里加入C99?因为我知道Microsoft编译器支持C99的一些东西。所以在那些MSDN页面中,有些东西也可能来自C99 - 虽然没有特别想到任何东西。在阅读它时以及将这些技术应用于自己的C ++代码时应该小心,这些代码是可移植的C ++。可能告知哪些部分是C99特定的,哪些不是。

测试标准一致性的小C ++片段的好地方是comeau online compiler。如果它被拒绝,可以肯定它不是严格的标准符合。

答案 1 :(得分:7)

当你有一个被编译器强制非内联的内联方法时,它会在每个使用它的编译单元中实例化该方法。今天,大多数编译器都足够聪明,只有在需要时才会实例化一个方法(如果使用的话),因此只包含头文件不会强制实例化。正如您所说,链接器将选择一个要包含在可执行文件中的实例 - 但请记住,对象模块中的记录是一种特殊类型(例如,COMDEF),以便为链接器提供足够的知道如何丢弃重复实例的信息。因此,这些记录不会导致模块之间出现不必要的依赖关系,因为链接器将使用优先级低于“常规”记录来解析依赖关系。

在你给出的例子中,你真的不知道,但没关系。链接器不会仅基于非内联实例解析依赖关系。结果(就链接器包含的模块而言)将与内联方法不存在一样好。

答案 2 :(得分:4)

AFAIK,没有关于C ++编译器如何以及何时内联函数调用的标准定义。这些通常是编译器无需遵循的“建议”。实际上,不同的用户可能想要不同的行为。一个用户可能关心速度,而另一个用户可能关心小的生成对象文件大小。此外,编译器和平台也不同。有些编译器可能会应用更智能的分析,有些可能不会。有些编译器可能会从内联生成更长的代码,或者在调用太昂贵的平台上工作等等。

当你有一个内联函数时,编译器仍然应该为它生成一个符号,并最终解析它的单个版本。因此,如果它在静态库中,人们仍然可以不在内联中调用该函数。换句话说,它仍然充当正常的功能,。

内联的唯一影响是在某些情况下,编译器会看到调用,看到内联,并完全跳过调用,但函数应该仍然存在,在这种情况下它不会被调用。

答案 3 :(得分:3)

答案 4 :(得分:2)

如果要确保将它们编译到特定的库中,请不要内联函数。