内联如何影响模块接口中的成员函数
How does inline affect member functions in module interfaces?
考虑头文件:
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
,以最大限度地减少未来的重构。
这是否意味着,在模块世界中,为了使编译器能够优化函数调用,我们必须将它们注释为
inline
,即使它们是在类中定义的?
在某种程度上。
内联是一种"好像"优化,如果编译器足够聪明,即使在翻译单元之间也可以进行内联。
也就是说,当在单个翻译单元中工作时,内联是最容易的。因此,为了促进简单的内联,inline
声明的函数必须在使用它的任何翻译单元中提供它的定义。这并不意味着编译器肯定会内联它(或者肯定不会内联任何非inline
限定的函数),但它确实使内联过程变得容易得多,因为内联发生在TU内,而不是在TU之间。
在预模块世界中,类中定义的类成员定义被隐式声明为inline
。为什么?因为定义在类中。在模块前的世界中,TU之间共享的类定义通过文本包含来共享。因此,类中定义的成员将在这些TU之间共享的头中定义。因此,如果多个TU使用同一个类,那么这些多个TUs是通过包括类定义及其在标头中声明的成员的定义来实现的。
也就是说,您无论如何都包含了的定义,那么为什么不将其设为inline
呢?
当然,这意味着函数的定义现在是类文本的一部分。如果更改在标头中声明的成员的定义,则会强制递归地重新编译包含该标头的每个文件。即使类的接口本身没有改变,您仍然需要重新编译。因此,隐式生成这样的函数inline
不会改变这一点,所以您还可以这样做。
为了在预模块世界中避免这种情况,您可以简单地在C++文件中定义成员,而不会将其包含在其他文件中。您失去了简单的内联,但获得了编译时间。
但事情是这样的:这是一个使用文本包含作为将类传递到多个位置的手段的工件。
在模块化的世界中,您可能希望在类本身中定义每个成员函数,正如我们在Java、C#、Python等其他语言中看到的那样。这使代码的局部性保持合理,并防止了必须重新键入相同的函数签名,从而满足DRY的需要。
但是,如果所有成员都是在类定义中定义的,那么根据旧规则,所有这些成员都将是inline
。为了让模块允许函数为inline
,二进制模块工件必须包括这些函数的定义。这意味着,每当你在这样的函数定义中更改哪怕一行代码时,都必须递归地构建模块,以及依赖它的每个模块。
删除模块中的隐式-inline
可以为用户提供与文本包含时代相同的功能,而无需将定义移出类。您可以选择哪些函数定义是模块的一部分,哪些不是。
这来自几天前刚刚在布拉格采用的P1779。来自提案:
本文建议从附加到(命名)模块的类定义中定义的函数中删除隐式内联状态。这使得类可以从避免冗余声明中受益,并保持模块作者在使用或不使用内联声明函数时提供的灵活性。此外,它允许注入的类之友模板(不能在类定义之外进行一般性定义)完全是非内联的。它还解决了NB注释US90。
该论文(除其他外)删除了以下句子:
在类定义中定义的函数是内联函数。
并添加了以下句子:
在全局模块中,类定义中定义的函数是隐式内联的([class.mfct],[class.friend])。
您的export module M
示例将是初始程序的模块等效程序。请注意,编译器已经执行了未注释inline
的内联函数,只是它们在启发式中额外使用了inline
关键字。
- 尝试导入pybind-opencv模块时出现libgtk错误
- C++核心准则 C35 对于接口类"A base class destructor should be either public and virtual, or protected and nonv
- Visual C++GC接口如何启用它以及要包含哪个库
- Windows.h与GLFW.h的接口
- 为什么当我解模块化时,这个C++代代码"效率较低"?
- 当字段可以为null时,如何使用C++接口在Avro中写入数据
- 如果C++对象的类在另一个boost模块中声明,如何使用boost将指向该对象的指针返回到python
- 提供与TMP和SFINAE的通用接口
- 为重写std::exception的库生成swig接口时出错
- 内联如何影响模块接口中的成员函数
- C++返回 Numpy 数组的 Python 扩展模块
- 当我尝试加载内核模块时,如何修复C++中的这个 malloc() 错误?
- COM 接口 c# 封送数组数组
- 如何在 SCIP C++ 接口中获取 MILP 约束矩阵中的系数值
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- INET无线接口表模块参数
- Python - 导入 c++ 模块接口 - 无法打开共享对象文件
- OmNET++动态检查NED模块是否实现了接口
- Python C 接口,不同的模块共享静态变量
- 从另一个模块获取派生类指针的接口