编译器是否更可能使用指定的inline关键字在类声明中内联函数?

JBe*_*Fat 4 c++ inline compiler-warnings compiler-optimization

我最近正在审查同事的代码,并注意到他已将"inline"关键字放在一组在类声明中定义的Getter函数之前.

例如

class Foo
{
public:
    inline bool GetBar() const { return m_Bar; }
private:
    bool m_Bar;
};
Run Code Online (Sandbox Code Playgroud)

我在代码审查中建议他删除内联关键字,因为我在许多不同的地方读过,在类声明中定义函数是由编译器解释的(在这种情况下是MSVC,但显然是C++标准的一部分) )作为作者想要内联函数的指示.我的感觉是,如果额外的文本不起任何作用,那就是不必要的混乱,应该删除.

他的回应如下:

  1. inline关键字使得与此代码交互的其他程序员清楚地知道这些函数是/应该内联的.

  2. 在这种情况下,许多编译器仍然会考虑内联关键字并使用它来影响(读取:增加)某种加权,用于确定所述函数是否实际上是内联的.

  3. 如果所述函数由于某种原因未被内联,则具有内联关键字意味着将"触发警告,如果未内联"警告将被触发(如果已启用).

就个人而言,我完全不同意第一个原因.对我来说,拥有类声明中定义的函数足以表明意图.

我对最后两个原因持怀疑态度.我找不到任何确认或否认有关影响某种加权的内联关键字的观点的信息.对于在类声明中定义的函数,我也无法触发"警告,如果没有内联"警告.

如果您已经阅读过这篇文章,我想知道您是否对上述任何一点有任何见解?另外,如果你能指出我的任何相关文章/文件,我真的很感激.

谢谢!

Mat*_*son 5

编辑1:添加了LLVM(换言之,"clang")内联代码

编辑2:添加关于如何"解决"此问题的说明.

实际上是正确的

第1点当然是不言自明的.

第2点是无意义的 - 所有现代编译器(至少MS,GCC和Clang [aka XCode])完全忽略inline关键字并完全基于频率/大小critera决定(根据大小*数字次确定"代码膨胀因子",如此小只调用几次的函数或函数更有可能被内联 - 当然,getter将是编译器内联的完美选择,因为它只有两三条指令,并且很可能比加载时短this,然后调用getter函数.

inline关键字不使在所有[C++标准状态,所述类中的定义是与任何区别inline反正].

第3点是另一个看似合理的场景,但我认为它的定义隐含内联的事实应该给出相同的结果.前inline一段时间有关于关键字及其在Clang邮件列表中的含义的讨论,结论是"编译器通常最了解".

inline与虚函数一起使用通常也是完全没用的,因为它们几乎总是通过vtable条目调用,并且不能内联.

编辑1:

代码取自LLVM的"InlineCost.cpp":

InlineCost InlineCostAnalysis::getInlineCost(CallSite CS, Function *Callee,
                                             int Threshold) {
  // Cannot inline indirect calls.
  if (!Callee)
    return llvm::InlineCost::getNever();

  // Calls to functions with always-inline attributes should be inlined
  // whenever possible.
  if (CS.hasFnAttr(Attribute::AlwaysInline)) {
    if (isInlineViable(*Callee))
      return llvm::InlineCost::getAlways();
    return llvm::InlineCost::getNever();
  }

  // Never inline functions with conflicting attributes (unless callee has
  // always-inline attribute).
  if (!functionsHaveCompatibleAttributes(CS.getCaller(), Callee,
                                         TTIWP->getTTI(*Callee)))
    return llvm::InlineCost::getNever();

  // Don't inline this call if the caller has the optnone attribute.
  if (CS.getCaller()->hasFnAttribute(Attribute::OptimizeNone))
    return llvm::InlineCost::getNever();

  // Don't inline functions which can be redefined at link-time to mean
  // something else.  Don't inline functions marked noinline or call sites
  // marked noinline.
  if (Callee->mayBeOverridden() ||
      Callee->hasFnAttribute(Attribute::NoInline) || CS.isNoInline())
    return llvm::InlineCost::getNever();

  DEBUG(llvm::dbgs() << "      Analyzing call of " << Callee->getName()
        << "...\n");

  CallAnalyzer CA(TTIWP->getTTI(*Callee), ACT, *Callee, Threshold, CS);
  bool ShouldInline = CA.analyzeCall(CS);

  DEBUG(CA.dump());

  // Check if there was a reason to force inlining or no inlining.
  if (!ShouldInline && CA.getCost() < CA.getThreshold())
    return InlineCost::getNever();
  if (ShouldInline && CA.getCost() >= CA.getThreshold())
    return InlineCost::getAlways();

  return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
}
Run Code Online (Sandbox Code Playgroud)

可以看出(在其余代码中进行了一些挖掘),只检查"始终"和"从不"内联选项.内联关键字本身没有.

[请注意,这是clang和clang ++的内联代码 - clang本身在生成代码时没有做任何特别聪明的事情,它只是"正在"(令数百名程序员在该项目上花了数百多年的时间!)解析器为C和转换为LLVM IR的C++,所有好的,聪明的东西都是在LLVM层完成的 - 这实际上是提供"多语言"编译器框架的好方法.我编写了一个Pascal编译器,尽管编译工作是一个完整的新手,但我的编译器(使用LLVM生成实际的机器代码)在基准(生成的代码)方面比Free Pascal更好 - 所有这都要归功于LLVM,几乎没有这是我的工作 - 除了一些代码在一个特定的基准测试中内联一些常用的函数]

我没有MS编译器的访问源(doh!),我不能为了检查这个而下载gcc.我知道,根据经验,所有三个内联函数都没有内联关键字,而gcc将积极内联函数,它可以确定只有一个调用者(例如大static帮助函数)

编辑2:

解决此类问题的正确方法是使用一个编码标准,清楚地说明inline应该使用何时何地[以及在类中定义的函数],以及何时不应该这样做.如果当前,其他类中没有其他小的getter函数都有inline它们,那么这个函数就会很奇怪并且很突出.如果除了一些以外都有inline,那么可能也应该修复.

另一个方面:我个人喜欢把if语句写成

if (some stuff goes here)
Run Code Online (Sandbox Code Playgroud)

(if和括号之间的空格,但不在内部的东西周围),但工作中的编码标准说:

if( some stuff goes here )
Run Code Online (Sandbox Code Playgroud)

(if和括号之间没有空格,但是里面有东西的空间)

我不小心得到了这样的东西:

if ( some stuff goes here )
Run Code Online (Sandbox Code Playgroud)

在某些代码中需要审核.我修复了这一行,但决定还修复175个其他if语句后用空格if- 在该文件中总共少于350个if语句,因此超过一半的错误是错误的...

  • @GregorMcGregor:我已经从LLVM添加了代码(截至上周五,给予或接受).我目前没有任何其他编译器项目的源代码,因此无法提供证据. (2认同)
  • 从2018年发布:https://blog.tartanllama.xyz/inline-hints/ - `inline`的存在肯定会影响编译器内联的决定(尽管它不是任何边际的唯一因素). (2认同)

Had*_*ais 3

对于您提到的特定示例,GetBar是隐式内联的,这使得inline关键字多余。C++ 标准第 7.1.2 P3 节说:

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

VC++文档也有同样的说明:

可以通过使用 inline 关键字或将函数定义放在类定义中来将类的成员函数声明为内联。

一般来说,您不需要使用关键字inline。AC/C++ 专家早在 2002 年就曾说过:

inline 关键字允许程序员告诉编译器一些它可能很难自动弄清楚的事情。然而,在未来,编译器可能能够比程序员更好地做出内联决策。当这种情况发生时,inline 关键字可能会被认为是一个奇怪的提醒,提醒程序员何时被迫担心代码生成的细节。

您可以在这里找到完整的文章。您应该阅读整篇文章以了解为什么在 C99 中添加该关键字。文章还讨论了extern inline函数。

现在我们处于未来,事实上,现代编译器非常复杂,不再需要此关键字。唯一的例外是使用extern inline函数时。

inline 关键字对编译器内联函数的决定有任何影响吗?

在 MSVC 中, inline 关键字可能会影响编译器的决定,尽管如果内联会造成净损失,编译器可能会选择忽略此提示

inline 关键字使与此代码交互的其他程序员清楚地知道这些函数是/应该内联的。

这是一个无效的理由。程序员是一个人。对于是否内联函数,人类通常很难做出比 MSVC 更好的决定。其次,当你告诉程序员应该内联一个函数时,他/她应该做什么?编译器是进行内联的编译器。该警告本身并没有告诉您是否必须对此采取任何措施,以及如果是这种情况该怎么办。

在这种情况下,许多编译器仍然考虑 inline 关键字,并使用它来影响(读:增加)某种权重,该权重用于决定所述函数实际上是否会被内联。

MSVC 中也是如此。但现代的 MSVC 不再需要这个提示了。如果内联函数有益,编译器会知道并内联它。如果不是,它将忽略人工插入的内联关键字。

那里有 inline 关键字意味着如果所述函数由于某种原因未内联,则将触发“如果未内联则发出警告”警告(如果启用)。

当选择内联的函数未内联时, MSVC 编译器会发出警告C4710。默认情况下禁用此警告,因为大多数开发人员不(也不应该)关心此警告。不过,您可以启用此警告。除非您是编译器优化研究人员并且想要了解 MSVC 使用的内联算法,否则不应启用这些警告。