内联在模块接口中的含义


24

考虑头文件:

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在适当的时候开始使用like,以最大程度地减少将来的重构。

Answers:


11

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

在某种程度上。

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

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

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

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

当然,这意味着函数的定义现在已成为类文本的一部分。如果更改标头中声明的成员的定义,则将强制递归地重新编译包含该标头的每个文件。即使类的界面本身没有变化,您仍然需要重新编译。因此,隐式地创建此类函数inline不会改变它,因此您也可以这样做。

为了避免在模块前世界中发生这种情况,您只需在C ++文件中定义该成员,该成员就不会包含在其他文件中。您失去了简单的内联,但是却获得了编译时间。

但这就是问题:这是使用文本包含作为将类传递到多个地方的一种手段的产物。

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

但是,如果所有成员都在类定义中定义,则在旧规则下,所有这些成员都将是inline。为了使模块允许使用某个功能inline,二进制模块工件必须包含这些功能的定义。这意味着,只要在这样的函数定义中更改一行代码,就必须递归地构建模块以及依赖于该模块的每个模块。

删除隐式inline模块将为用户提供与文本包含日相同的功能,而无需将定义移出类。您可以选择哪些函数定义是模块的一部分,哪些不是。


8

它来自几天前刚在布拉格采用的P1779。从提案中:

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

该文件(除其他事项外)删除了以下句子:

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

并添加句子:

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


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


那么,没有inline关键字的模块中的函数将永远不会被编译器内联,对吗?
metalfox

1
@metalfox不,我不认为这是正确的。
巴里

1
我知道了。谢谢。就像在cpp文件中定义的一样,这不一定意味着它不会在链接时内联。
metalfox
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.