有时,我想在头文件中有一个函数(包含在多个不同的翻译单元中),而不告诉编译器内联它(例如在仅头文件中)。当以 C 样式的方式执行此操作时,这很容易,只需声明函数static
,例如:
struct somedata { ... }
static somefunc (somedata *self) { ... }
这允许编译器决定是否内联函数,同时仍然允许在多个翻译单元中对函数进行多个定义,因为它没有外部链接。并且还允许我使用在其他翻译单元中创建的somedata
结构调用此函数,因为这些类型是兼容的。
我的问题是,如何使用类和方法执行此操作?例如,以这个头文件为例,它使用类和方法而不是函数和显式对象指针实际上是相同的:
struct someclass {
void method ();
}
void someclass::method () { ... }
显然,我不能使用static someclass::method
,因为那完全是另一回事。
我也不能将其放入匿名命名空间中,因为这样我在不同的翻译单元中得到不同的struct someclass
类型,即我不能在另一个文件中使用一个文件中的someclass *
,因为它们将是不同的(和不兼容的)类型。
我可以inline
声明所有这些方法,这将起作用,但会产生不良影响,即要求编译器内联它们,即使这毫无意义。
我是否错过了一些明显的东西,或者C++没有等效的方法static
?在我看来(希望是错的),我唯一的选择是将所有这些方法移动到一个单独的翻译单元中,或者将它们全部标记为内联 - 似乎没有等效的 C 风格的内部链接。
更新:我认为这个问题因重复而过早关闭。所谓的重复问题是关于将函数标记为内联是否总是内联它们。这个问题是关于如何避免 ODR 规则,就像普通函数的静态关键字所做的那样,或者解释这不能在C++中完成,这两个问题都没有被另一个问题回答,它只是告诉提问者不要担心它。在我的问题中,内联仅作为可能(但糟糕)的解决方案被提及。
更新 2:已经多次提到内联不是函数内联的请求,或者 C 标准只使用内联来绕过 ODR 规则,而不要求编译器内联函数。
这两种说法显然都是不真实的。例如,仔细阅读GCC文档或LLVM源代码会发现广泛使用的编译器确实inline
视为内联函数的请求。我还引用了 C++03,它说(在 7.1.2.2 中):
[...]内联说明符向实现指示,在调用点对函数体进行内联替换比通常的函数调用机制更可取。[...]
因此,现有的编译器和C++标准(我只检查了148882:2003)显然不同意"内联仅影响ODR"的反复声明。
这种错误的看法似乎相当普遍,例如:https://blog.tartanllama.xyz/inline-hints/有人通过查看实际的GCC/LLVM源代码来调查此声明,并发现两个编译器都将内联视为实际的内联请求。
但是,请记住,我的问题是关于如何在C++中获得成员函数的static
效果,或者获得更明确的声明,即C++根本没有成员函数的此功能,仅适用于普通函数。inline
的属性在这里仅与以下程度相关:它确实以牺牲可能不需要的内联为代价来解决问题,这可能会对性能不利。
对我来说,相对清楚的是,没有办法绕过一个定义规则。我不清楚的是,是否真的没有其他方法可以达到这种效果。例如,下一个最好的static
是匿名命名空间,但这也不起作用,因为它使其中声明的结构在不同的翻译单元之间都不兼容,因此它们不能互换。
我希望可能有一种方法可以解决它,例如,将结构放在匿名命名空间之外并在内部有一个派生类,或者其他一些构造,但目前我看不出如何,同时我不能排除它可能 - 因此这个问题。
更新 3:澄清示例 - 方法不包含静态变量,最终结果是否会导致方法的多个物理不同副本并不重要,只要所有此类副本的行为相同。这种方法的一个实际示例是:
char *reserve (int bytes)
{
if (left <= bytes)
flush ();
if (left <= bytes)
throw std::runtime_error ("bulkbuf allocation overflow");
return cur;
}
如果经常调用此方法(在源代码中),但不经常调用(在运行时),则要求编译器无缘无故地内联此方法可能会损害性能,当然也会损害代码大小。
更新 4:有许多重复的说法,编译器普遍忽略内联关键字作为内联请求,尽管有充分证据表明这是不正确的。
只是为了摆脱任何疑虑,我用这个(相当荒谬的)程序进行了尝试:
//inline
int f(int i)
{
return i < 0 ? 0 : f(i-1) + 1;
}
int main(int argc, char *[])
{
return f(5) + f(argc);
}
请注意注释掉的inline
关键字。
当我使用 Debian GNU/Linux Stretch(使用g++ -Os -S -o - test.C
)的 g++ 6.3.0(2016 年发布)编译它时,当inline
被注释掉时,我会得到这个main
程序:
movl %edi, %ecx
movl $5, %edi
call f(int)
movl %eax, %edx
movl %ecx, %edi
call f(int)
addl %edx, %eax
ret
当内联在以下位置处于活动状态时:
xorl %eax, %eax
.L3:
cmpl %eax, %edi
js .L2
incl %eax
jmp .L3
.L2:
addl $6, %eax
ret
因此,如果没有inline
函数就不会内联,使用inline
,它确实被内联了。至少,这证明了编译器不会像通常声称的那样普遍忽略内联作为内联请求(g++ 肯定是为数不多的主要C++编译器之一,版本 6.3 几乎没有过时,所以这不是一个奇怪的利基编译器)。
所以事实是,标准编译器和现有编译器都确实将内联视为不仅仅是 ODR 行为更改,即作为内联函数的显式请求。
请注意,编译器是否忽略提示仅与我的问题无关,这是关于C++语言,而不是任何编译器,至少 C++03 要求编译器"优先"内联函数标记为这样,而不要求他们这样做,所以我对内联的担忧是有效的,无论编译器是否忽略它。
更新 5:
将f
更改为:
返回 i <0 ? 1 : f(i-1) + f(i-2);
导致 CLang++ 3.8.1-24 和 G++ 的模拟行为。另外,与 c++11 兼容的编译器是否总是忽略内联提示?声明 MSVC 还将内联关键字作为实际内联的请求。
g++,clang++/LLVM和MSVC共同覆盖了C++"市场"的很大一部分,因此可以肯定地说,编译器几乎普遍将内联视为内联请求,无论他们是否注意它,以及C++标准的其他要求。
非成员函数的static
语义和inline
是不同的,即使函数定义在其他方面相同。
// in several translation units
static void foo_static() {
static int bar; // one copy per translation unit
}
// in several translation units
inline void foo_inline() {
static int bar; // one copy in the entire program
}
&foo_static
翻译单元之间也会有所不同,而&foo_inline
将是相同的。
无法为成员函数(即使对于静态成员函数)请求static
语义。
也没有办法在不实际(显式或隐式)inline
声明的情况下为任何函数请求inline
语义。换句话说,没有办法说"让这个函数在除了实际内联之外的所有事情上都像inline
一样"。
另一方面,函数模板的语义类似于inline
函数的语义,而无需请求编译器(无论现在多么无意义)在其调用站点内联它们。
// in several translation units
template <nullptr_t=nullptr>
void foo_template() {
static int bar; // one copy in the entire program
}
将其标记为inline
.
如果将函数标记为static
则会在包含该标头的每个翻译单元中获得该函数的单独副本;因此,您将在可执行文件中拥有该函数的多个副本。如果将其标记为inline
并且编译器未内联扩展它,则无论包含该标头的翻译单元数,都将在可执行文件中只获得它的一个副本。