事实上,有一个定义规则说,必须在每个使用它的翻译单元中定义一个内联函数。血淋淋的细节如下。首先3.2/3
:
每个程序都应包含该程序中使用的每个非内联函数或对象的一个定义;无需诊断。定义可以显式出现在程序中,可以在标准或用户定义库中找到,或者(在适当时)隐式定义(参见 12.1、12.4 和 12.8)。内联函数应在使用它的每个翻译单元中定义。
当然7.1.2/4
:
内联函数应在使用它的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义(3.2)。[注意:在其定义出现在翻译单元之前,可能会遇到对内联函数的调用。] 如果具有外部链接的函数在一个翻译单元中声明为内联,则应在其出现的所有翻译单元中声明为内联;不需要诊断。具有外部链接的内联函数在所有翻译单元中应具有相同的地址。外部内联函数中的静态局部变量始终引用同一个对象。外部内联函数中的字符串文字是不同翻译单元中的相同对象。
但是,如果您在类定义中定义函数,它会被隐式声明为inline
函数。这将允许您在程序中多次包含包含该内联函数体的类定义。由于该函数具有external
链接,因此它的任何定义都将引用相同的函数(或更血腥 - 指向相同的函数entity
)。
关于我的索赔的血腥细节。首先3.5/5
:
此外,如果类的名称具有外部链接,则成员函数、静态数据成员、类或类范围的枚举具有外部链接。
然后3.5/4
:
如果具有命名空间范围的名称是 [...] 命名类(第 9 条)的名称,或者是在 typedef 声明中定义的未命名类,其中该类具有用于链接目的的 typedef 名称,则该名称具有外部链接。
这个“用于链接目的的名称”很有趣:
typedef struct { [...] } the_name;
由于现在您的程序中有同一实体的多个定义,ODR 的另一件事恰好限制了您。3.2/5
接下来是无聊的东西。
如果每个定义出现在不同的翻译单元中,程序中可以有多个类类型(第 9 条)、枚举类型(7.2)、带有外部链接的内联函数(7.1.2)[...] 的定义,并提供定义满足以下要求。给定这样一个名为 D 的实体在多个翻译单元中定义,则
- D 的每个定义都应由相同的记号序列组成;和
- 在 D 的每个定义中,根据 3.4 查找的相应名称应指在 D 的定义中定义的实体,或应指同一实体,在重载决议 (13.3) 和部分模板特化匹配后 (14.8) .3) [...]
我现在切断了一些不重要的东西。以上是关于内联函数需要记住的两个重要内容。如果您多次定义 extern 内联函数,但确实以不同方式定义它,或者如果您定义它并且其中使用的名称解析为不同的实体,那么您正在执行未定义的行为。
函数必须在使用它的每个 TU 中定义的规则很容易记住。并且它是相同的也很容易记住。但是那个名字解析的东西呢?这里有一些例子。考虑一个静态函数assert_it
:
static void assert_it() { [...] }
现在,既然static
会给它内部链接,当你将它包含到多个翻译单元中时,每个定义都会定义一个不同的实体。这意味着您不允许使用assert_it
将在程序中多次定义的 extern 内联函数:因为发生的情况是内联函数将引用assert_it
一个 TU 中调用的一个实体,但引用相同的另一个实体在另一个 TU 中命名。你会发现这都是无聊的理论,编译器可能不会抱怨,但我发现这个例子特别显示了 ODR 和实体之间的关系。
接下来是再次回到您的特定问题。
以下是相同的事情:
struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!
但是这个不同,因为该函数是非内联的。您将违反 ODR,因为您有多个定义f
是否多次包含标题
struct A { void f(); }; void A::f() { } // evil!
现在,如果您在类内部inline
声明 of f
,然后省略在标题中定义它,那么您就违反了3.2/3
(并且7.1.2/4
说的是同一件事,只是更详细),因为该函数未在该翻译单元中定义!
请注意,在 C (C99) 中,内联具有与 C++ 不同的语义。如果你创建一个外部内联函数,你应该首先阅读一些好论文(最好是标准),因为这些在 C 中真的很棘手(基本上,任何使用的函数内联定义都需要另一个非内联函数定义在另一个TU. C 中的静态内联函数很容易处理。除了具有通常的“内联替换”提示之外,它们的行为与任何其他函数一样inline
。C 和 C++ 中的静态仅用作内联替换提示。因为静态已经创建任何时候使用不同的实体(由于内部链接),inline
只会添加内联替换提示 - 不多。