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; }
第二个项目是第一个.我如何知道编译器将使用哪个定义,以及哪些目标文件与其依赖关系将被链接?那个标准上有什么标准吗?(试过,但未能找到)
谢谢
编辑:因为我看到有些人误解了问题所在,我想强调:如果编译器决定为该函数创建一个符号(在这种情况下,由于'虚拟',它将会是不同目标文件中的几个(外部看到的)实例化,链接器选择哪个定义(从哪个目标文件?)?)
只是我的两分钱.这不是特别关于虚函数,而是关于内联函数和成员函数.也许它很有用.
就标准C++而言,必须在使用它的每个翻译单元中定义内联函数.并且非静态内联函数将在每个转换单元和相同的地址中具有相同的静态变量.编译器/链接器必须将多个定义合并到一个函数中才能实现此目的.因此,总是将内联函数的定义放入标题中 - 或者如果仅在实现文件(".cpp")中定义它(对于非成员函数),则不将其声明放入标题中,因为如果你会有人使用它,你会得到一个关于未定义函数或类似东西的链接器错误.
这与非内联函数不同,非内联函数必须在整个程序中仅定义一次(单定义规则).对于内联函数,如上所述的多个定义是正常情况.并且这与呼叫是否是内部内联无关.关于内联函数的规则仍然很重要.Microsoft编译器是否遵守这些规则 - 我无法告诉您.如果它在这方面符合标准,那么它就会.但是,我可以想象使用虚拟,dll和不同TU的某些组合可能会有问题.我从未测试过,但我相信没有问题.
对于成员函数,如果在类中定义函数,则它是隐式内联的.并且因为它出现在标题中,所以必须在使用它的每个翻译单元中定义它的规则被自动满足.但是,如果您在类外定义函数并在头文件中定义(例如,因为存在循环依赖关系,其中包含代码),如果您多次包含相应的文件,那么该定义必须是内联的,避免链接器抛出的多重定义错误.文件示例f.h
:
struct f { // inline required here or before the definition below inline void g(); }; void f::g() { ... }
这与将定义直接放入类定义具有相同的效果.
请注意,有关内联函数的规则对于C99而言比对C++更复杂.这里,内联函数可以定义为内联定义,在整个程序中可以存在多个内联定义.但是,如果使用这样的(inline-)定义(例如,如果它被称为),则存在必须也正好一个外部定义在包含在另一翻译单元整个程序.基本原理(引用PDF解释几个C99功能背后的基本原理):
C99中的内联确实以两种方式扩展了C++规范.首先,如果函数在一个转换单元中内联声明,则不需要在每个其他转换单元中内联声明.例如,这允许库函数在库中内联,但仅通过其他地方的外部定义可用.对外部函数使用包装函数的替代方法需要一个额外的名称; 如果翻译者实际上没有进行内联替换,它也可能会对性能产生负面影响.
其次,要求内联函数的所有定义"完全相同"的要求被程序行为不应取决于是否使用可见内联定义或外部定义实现调用的要求所取代.功能.这允许内联定义专门用于在特定翻译单元内使用.例如,库函数的外部定义可能包括一些参数验证,这些验证对于从同一库中的其他函数进行的调用不需要.这些扩展确实提供了一些优势; 关注兼容性的程序员可以简单地遵守更严格的C++规则.
为什么我在这里加入C99?因为我知道Microsoft编译器支持C99的一些东西.所以在那些MSDN页面中,有些东西也可能来自C99 - 虽然没有特别想到任何东西.在阅读它时以及将这些技术应用于自己的C++代码时应该小心,这些代码是可移植的C++.可能告知哪些部分是C99特定的,哪些不是.
一个测试标准一致性的小C++片段的好地方是comeau在线编译器.如果它被拒绝,可以肯定它不是严格的标准符合.
当你有一个内联方法被编译器强制非内联时,它将真正实例化每个使用它的编译单元中的方法.今天,大多数编译器都足够聪明,只有在需要时才会实例化一个方法(如果使用的话),因此只包含头文件不会强制实例化.正如您所说,链接器将选择一个要包含在可执行文件中的实例 - 但请记住,对象模块中的记录是一种特殊类型(例如,COMDEF),以便为链接器提供足够的知道如何丢弃重复实例的信息.因此,这些记录不会导致模块之间出现不必要的依赖关系,因为链接器将使用优先级低于"常规"记录来解析依赖关系.
在你给出的例子中,你真的不知道,但没关系.链接器不会仅基于非内联实例解析依赖关系.结果(就链接器包含的模块而言)将与内联方法不存在一样好.