我该在头文件中定义静态内联方法吗?

23

我读过关于不要在头文件中定义任何东西的文章,因为对于包括该头文件的每个其他文件都会产生冗余副本。但是,在静态内联方法的情况下,似乎我必须立即进行定义(至少Visual Studio 2010不允许我这样做)。因此,如果我在头文件中编写接口,我就无法在类定义外或. cpp文件中定义静态内联方法。

那么,我是否应该使用静态内联方法?还有一个相关问题:我应该在头文件中定义任何方法或变量吗(常量呢)?

无论如何,奇怪的是,我的C ++书籍并没有详细介绍这个问题。

编辑:我阅读了关于静态内联方法的类似问题,但其中没有一个直接解决这个问题。


2
如果您不将定义放在头文件中,则编译器无法进行内联... - user529758
1
如果你想要内联某个东西,那就总是会涉及到冗余的拷贝。 - leftaroundabout
1
在头文件中定义那些既不是内联函数也没有内部链接的东西存在的问题并不是副本是多余的。问题在于这些副本是有害的,会导致冲突符号和目标文件无法链接在一起。然而,在静态内联函数的情况下,这些副本并不会有害,它们是使函数成为静态内联的全部目的。 - Steve Jessop
2个回答

45
如何在头文件中添加函数定义?
有三种可能的方法:
1. 将函数标记为`inline` 2. 将函数声明为`static` 3. 将函数放在匿名命名空间中
那么正确的做法是什么呢?
方法一即将函数标记为`inline`,这是不破坏“单一定义规则”的正确方式。
其他两种方法有什么问题?
在第二种和第三种方法中,每个翻译单元将包含其自己版本的函数。因此,程序将包含多个不同版本的函数,从而导致生成的二进制文件大小增加。
对于一个`static`函数fun(),每个翻译单元中的`&fun`都是不同的,程序将包含N个不同版本的函数。此外,如果函数包含静态局部变量,则将有N个不同的静态局部变量,每个函数实例一个。
第一种方法如何避免这个问题?
内联函数具有外部链接性。当将函数标记为`inline`时,在所有翻译单元中该函数将具有相同的地址。此外,在内联函数体内定义的静态局部变量和字符串字面量在翻译单元之间被视为同一对象。简而言之,内联函数在所有翻译单元中具有相同的地址。
那么,在头文件中使用`static inline`函数定义有什么意义呢?static关键字强制函数具有内部链接。作为内联定义的函数的每个实例都被视为一个单独的函数,并且每个实例都有自己的静态局部变量和字符串字面量的副本。因此,这类似于#2
需要注意的是,标准规定在用户程序中所有的inline函数定义必须在使用或调用该函数的所有翻译单元中具有完全相同的定义。
相关标准文献引用: C++03标准 3.2 One definition rule: Para 3: 每个程序必须包含每个非内联函数或对象的确切定义,该程序中使用该函数或对象;不需要诊断。该定义可以显式地出现在程序中,可以在标准库或用户定义的库中找到,或者(适当时)是隐式定义的(请参见12.1、12.4和12.8)。内联函数必须在使用它的每个翻译单元中定义。
7.1.2 Function specifiers Para 4: 内联函数必须在使用它的每个翻译单元中定义,并且在每种情况下都必须具有完全相同的定义(3.2)。[注意:可以在翻译单元中出现内联函数的调用,而其定义尚未出现。]如果在一个翻译单元中声明了具有外部链接的内联函数,则必须在所有出现的翻译单元中声明具有内联函数;不需要诊断。具有外部链接的内联函数在所有翻译单元中都应具有相同的地址。extern inline函数中的静态局部变量始终引用同一对象。在extern inline函数中的字符串字面值是不同翻译单元中的同一对象。

我有点困惑。Steve Jessop 上面似乎暗示静态内联函数的副本并不会有害,但你似乎在暗示相反的情况。此外,leftaroundabout 暗示内联方法(可能仅在头文件中定义而不是 cpp 文件中)总是涉及冗余副本,而你似乎在暗示相反的情况。 - Some Newbie
@SomeNewbie:我认为Steve所说的“有害”是指链接问题。使用static inline,就不会有任何链接问题,这也是我的答案所说的。但问题在于,每个实例都将有其自己的静态局部变量和字符串字面值的副本,是否使用取决于您是否需要此行为。 - Alok Save
@SomeNewbie:当你将一个函数标记为“inline”时,不会有冗余的副本,该函数在所有翻译单元中都具有相同的地址。但是,用户有责任确保该函数在所有翻译单元中具有相同的定义。 - Alok Save
@Als:是的,你说得对,我所指的是这个。我并不认为代码膨胀是“有害”的,因为与链接问题不同,它不会阻止程序编译。膨胀是您和编译器之间的另一个争论点。通常情况下,这并不重要——如果函数实际上被内联,则会有多个(大多数)副本散落在各处。有时候很重要,使用inline只是一种预防措施。inline函数的缺点(正如您所指出的)是定义必须在所有TUs中完全相同:比static函数更强的兼容性限制。 - Steve Jessop
“将函数标记为内联是正确的方法,可以避免破坏ODR规则。” - Plus One。我之前没有意识到ODR也适用于函数。还请参阅CFE-Users邮件列表中的头文件中的静态内联函数和-Wunused-function - jww
显示剩余2条评论

7
(1) 我无法在类定义外或在.cpp文件中定义静态内联方法。 你可以在头文件中类定义外部定义一个静态内联方法。演示Demo。但是你不能在.cpp文件中定义它们。
(2) 我应该使用静态内联方法吗? 我认为,可以很容易地避免使用它们。只有在需要在头文件中看到函数体时,才将它们定义为inline
(3) 在头文件中甚至应该定义任何方法或变量吗(如常量)? 1. 在类定义内部,你可以定义整数类型的static const数据。 2. static方法可以在类定义内部定义。 3. static inline方法可以在类定义内部或者在头文件中类定义之外定义。 4. 必须在单个.cpp文件中定义static数据成员以符合“一个定义规则”。

如果您需要为自己的目的查看头文件中的函数体,则只需将它们设置为内联。那么模板函数呢? - Alok Save
在类体内,您可以定义整数类型的静态常量数据。这在C++11中已经改变了 - Alok Save
模板默认情况下不是inline。如果您想让一个模板函数作为inline,则必须显式地将其标记为inline。在类定义内部定义的函数默认为inline - Alok Save
@Als,不对。template函数默认是inline的。在template函数中提到inline多余的。我们正在谈论inline关键字的保证效果,它确保在所有翻译单元中只生成1个定义。这里是线程,如果一个template被完全专门化,则只有inline关键字有意义。 - iammilind
好吧,我想我会坐在这里直到达成协议。 :) - Some Newbie
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接