27

考虑头文件:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept : ID(ID_) {}

  int GetID() const noexcept { return ID; }
};

或者,或者:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept;

  int GetID() const noexcept;
};

inline T::T(int const ID_) noexcept : ID(ID_) {}

inline int T::GetID() const noexcept { return ID; }

在预模块世界中,这些标头可能以文本形式包含在多个 TU 中,而不会违反 ODR。此外,由于涉及的成员函数相对较小,编译器可能会“内联”(使用时避免函数调用)这些函数,甚至优化掉一些实例T

在最近关于完成 C++20 的会议的报告中,我可以阅读以下声明:

我们澄清了inline模块接口的含义:意图是没有显式声明的函数体不是inline模块 ABI 的一部分,即使这些函数体出现在模块接口中。为了让模块作者对他们的 ABI 有更多的控制,模块接口中类体中定义的成员函数不再是隐式inline的。

我不确定我没有弄错。这是否意味着,在模块世界中,为了让编译器能够优化函数调用,我们必须对它们进行注释,inline即使它们是在类中定义的?

如果是这样,下面的模块接口是否等同于上面的标题?

export module M;

export
class T
{
private:
  int const ID;

public:
  inline explicit T(int const ID_) noexcept : ID(ID_) {}

  inline int GetID() const noexcept { return ID; }
};

尽管我仍然没有支持模块的编译器,但我想inline在适当的时候开始使用这样的方法,以尽量减少未来的重构。

4

2 回答 2

13

这是否意味着,在模块世界中,为了让编译器能够优化函数调用,我们必须对它们进行注释,inline即使它们是在类中定义的?

在某种程度上。

内联是一种“好像”的优化,如果编译器足够聪明,内联甚至可以在翻译单元之间发生。

话虽如此,在单个翻译单元中工作时,内联是最容易的。因此,为了促进简单的内联,一个inline-declared 函数必须在使用它的任何翻译单元中提供其定义。这并不意味着编译器一定会内联它(或者肯定不会内联任何非inline限定函数),但它确实使内联过程变得更容易,因为内联发生在一个 TU 内而不是它们之间。

类中定义的类成员定义,在模块前的世界中,是inline隐式声明的。为什么?因为定义在类中。在模块前的世界中,在 TU 之间共享的类定义通过文本包含共享。因此,在类中定义的成员将在这些 TU 之间共享的标头中定义。因此,如果多个 TU 使用同一个类,则这些多个 TU 是通过在标头中包含类定义及其成员的定义来实现的。

也就是说,无论如何你都包含了定义,那为什么不做呢inline

当然,这意味着函数的定义现在是类文本的一部分。如果您更改在标头中声明的成员的定义,这将强制重新编译包含该标头的每个文件,递归。即使类的接口本身没有改变,你仍然需要重新编译。所以隐式地制作这样的函数inline并不会改变这一点,所以你也可以这样做。

为避免在预模块世界中出现这种情况,您可以简单地在 C++ 文件中定义成员,该成员不会包含在其他文件中。你失去了简单的内联,但你获得了编译时间。

但事情是这样的:这是使用文本包含作为将课程传递到多个地方的手段的产物。

在模块化世界中,您可能希望在类本身中定义每个成员函数,正如我们在 Java、C#、Python 等其他语言中看到的那样。这使代码局部性保持合理,并避免重新键入相同的函数签名,从而满足 DRY 的需求。

但是如果所有成员都在类定义中定义,那么根据旧规则,所有这些成员都是inline. 并且为了让模块允许函数成为inline,二进制模块工件必须包含这些函数的定义。这意味着,只要您在这样的函数定义中更改了一行代码,就必须递归地构建模块以及依赖于它的每个模块。

删除隐含inline模块赋予用户与文本包含时代相同的权力,而无需将定义移出类。您可以选择哪些函数定义是模块的一部分,哪些不是。

于 2020-02-18T14:41:19.310 回答
8

这来自P1779,几天前刚刚在布拉格被采用。从提案中:

本文建议从附加到(命名)模块的类定义中定义的函数中删除隐式内联状态。这允许类从避免冗余声明中受益,保持模块作者在声明有或没有内联函数时提供的灵活性。此外,它允许类模板的注入朋友(不能在类定义之外进行一般定义)完全非内联。它还解决了 NB 评论US90

论文(除其他外)删除了以下句子:

在类定义中定义的函数是内联函数。

并添加了句子:

在全局模块中,在类定义中定义的函数是隐式内联的([class.mfct]、[class.friend])。


您的示例export module M将是初始程序的模块化等效项。请注意,编译器已经执行了未注释inline的内联函数,只是它们inline在启发式方法中另外使用了关键字的存在。

于 2020-02-18T14:10:40.723 回答