为什么C++17没有放弃一次定义规则?

6
引用C++草案N4713:
每个程序都应该包含在程序中odr-used的非内联函数或变量的唯一定义,除了在丢弃语句(9.4.1)之外。不需要诊断。定义可以明确出现在程序中,可以在标准或用户定义库中找到,或者(在适当的情况下)它是隐式定义的(参见15.1、15.4和15.8)。内联函数或变量应在每个翻译单元中定义,其中它在丢弃语句之外被odr-used。
在C++17之前的C++版本中,我可以通过只声明我的函数为inline来规避这个限制。C++17为变量添加了相同的功能。
此外,我认为inline关键字除了使忽略ODR成为可能之外,没有其他作用。
那么,为什么不在C++17中放弃整个规则呢?我看不出一个可以关闭的规则的目的。

10
如果被弃用了,那么当在不同翻译单元中有多个定义相同但实现不同的符号发生冲突时会怎样? - Matthieu Brucher
5
为什么应该放弃它? - L. F.
即使使用未来模块,我们仍然可能破坏ODR :-/ - Jarod42
@Jarod42 但是使用模块 - ODR违规应该(这是我的猜测)在编译时被识别,根据http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1103r2.pdf:`变量、函数、类类型、枚举类型或模板不得在先前定义可达的地方进行定义(100.6)`。因此,也许ODR违规最终将成为编译器错误。 - marcinj
@marcinj:一些ODR违规可能很容易通过模块检测到,但我们仍然可以以许多方式破坏ODR。 - Jarod42
显示剩余2条评论
4个回答

15
使用内联inline关闭ODR并不是免费的:必须在每个翻译单元中都存在内联实体的定义。请注意,这意味着对其定义的任何更改都会导致使用它的每个编译单元重新编译。当函数是某些库所依赖的许多/大型项目的一部分时,这将特别令人不愉快。
另一方面,非内联函数仅存在于一个编译单元中,并通过某些符号由链接器在其他地方引用。遵守ODR可以保证该符号不会有歧义。

3
定义必须在所有翻译单元中完全相同。 - rustyx
1
@rustyx 没错。当然,“inline”并没有真正关闭ODR,这就是为什么我用引号括起来的原因。在相对高层次的问答范围内,inline语义的确切细节并不是非常相关,所以为了简洁和清晰起见,我更愿意跳过它们。 - Baum mit Augen
8
ODR并不保证符号不会有歧义,它要求符号没有歧义。责任在程序员身上。ODR违规不一定需要诊断,而且会导致未定义行为。其中一个实际的结果是当你有两个不同的“相同”函数定义时,你不知道哪一个将被链接。 - Pete Becker
@PeteBecker 这可能是我的语言问题。我试着修复它。 - Baum mit Augen
@BaummitAugen -- 是的,这样更好。我会把我的评论留作详细说明。 - Pete Becker
@PeteBecker 谢谢。我以为遵从是隐含的,就像“交通法规保证你可以安全通过绿灯 *(除非有人闯红灯,这是隐含的)*”。每天都会学到新东西。 :) - Baum mit Augen

5

inline 是危险且代价高昂的。

它很昂贵,因为每个使用某个东西的编译单元现在都依赖于该东西的定义。所以改变函数体?重新编译使用它的每个用户。

它很危险,因为如果两个 inline 定义不一致,你的程序就会出现 IF-NDR(ill formed, no diagnostic required)错误。

没有 inline,两个定义会导致一个 ill formed 程序,但编译器必须提供诊断信息;通常是一个硬错误。

inline 关闭了这种非常有用的警告。

如果每个编译器都能将不同的 inline 定义的 IF-NDR 转换为诊断错误消息,那么你会有更多的案例。只要证明难以实现,或者未实现,inline 就是一个 "激活不安全模式!" 的选项。将其设置为默认值将是逆生产的。


深入探讨Yakk的观点,即“inline”是昂贵的:如果程序员选择使用内联而没有进行性能分析和评估,则在任何中等大小的项目或更大的项目中,成本远远超过未知的性能收益(如果有)。对于那些可以利用WPO或LTO的可执行文件,“整个程序优化”(WPO)或“链接时优化”(LTO)可能会使任何这样昂贵的收益无效。除非具有可衡量的好处,否则避免使用“inline”,或者如果您的项目很小,编译开销不是问题,或者如果您的时间是免费的。 - Eljay
@Eljay 或者当需要在头文件中定义时。 - apple apple

1

权衡之处在于你需要在每个使用处都定义一个inline函数的定义。如果你想这样做,只需将整个程序放在一个.cpp文件中。

ODR是你需要进行分离编译的内容,这仍然很有用。


1

来自cppreference

每个非内联函数或变量的仅有一个定义(参见下文)必须出现在整个程序中(包括任何标准和用户定义的库)。编译器不需要诊断此违规行为,但违反此行为的程序的行为是未定义的。

将函数声明为inline并不会“忽略”ODR,但它会导致每个函数出现为其自己的实体,并且需要在使用它的每个翻译单元中都进行定义。这是微小但重要的区别。ODR仍然需要有单独的翻译单元。


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