方法的静态函数的等价性



有时,我想在头文件中有一个函数(包含在多个不同的翻译单元中),而不告诉编译器内联它(例如在仅头文件中)。当以 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并且编译器未内联扩展它,则无论包含该标头的翻译单元数,都将在可执行文件中只获得它的一个副本。

相关内容

  • 没有找到相关文章

最新更新