【问题标题】:Inline functions in C++C++ 中的内联函数
【发布时间】:2023-03-10 02:55:01
【问题描述】:

如果我们在类定义本身中定义一个成员函数,它是否必须被内联处理,或者它只是对编译器的一个可以忽略的请求。

【问题讨论】:

标签: c++ inline-functions


【解决方案1】:

是的,在类体内定义的函数隐式为inline

(与声明为inline 的其他函数一样,这并不意味着编译器必须在调用函数的地方执行内联扩展,它只是启用“单一定义规则”的允许放宽,并结合要求定义必须包含在使用该函数的所有翻译单元中。)

【讨论】:

    【解决方案2】:

    正如其他人所说,在类中定义的方法会自动内联请求。 了解原因很有用。

    假设不是。您必须为这样的函数生成代码,并且无论在何处调用它,跳转到子程序的指令都必须通过链接器引用该位置。

    class A {
    public:
      void f() { ... your code ... }
    };
    

    每次看到这段代码,如果它不是内联的,编译器只能假设它必须被生成,所以它会生成一个符号。假设它是这样的:

    A__f_v:

    如果那个符号是全局的,那么如果你碰巧在不同的模块中多次包含这个类代码,你会在链接时遇到多重定义的符号错误。所以它不可能是全球性的。相反,它是本地文件。

    假设您将上述头文件包含在多个模块中。在每一个中,它将生成该代码的本地副本。这比根本不编译要好,但是当你真的只需要一个时,你会得到多个代码副本。

    这导致以下结论:如果您的编译器不打算内联函数,那么最好在某处声明一次,而不是请求内联。

    不幸的是,内联和非内联是不可移植的。它由编译器编写者定义。一个好的经验法则是始终使每个内联,特别是所有本身只是调用函数的函数,内联,因为你消除了开销。任何低于三行线性代码的内容几乎都可以。但是如果你的代码中有一个循环,问题是编译器是否允许它内联,更重要的是,即使它做了你想要的,你会看到多少好处。

    考虑一下这个内联代码:

    inline int add(int a, int b) { return a + b; }
    

    它不仅几乎与源代码中的原型一样小,而且内联代码生成的汇编语言也比对例程的调用要小。所以这段代码更小,更快。

    而且,如果你碰巧传入了常量:

    int c= add(5,4);
    

    在编译时就解决了,没有代码。

    在 gcc 中,我最近注意到即使我不内联代码,如果它是文件本地的,他们无论如何都会偷偷地内联它。只有当我在单独的源模块中声明该函数时,它们才不会优化调用。

    另一方面,假设您在 1000 行代码中请求内联。即使你的编译器傻到可以配合它,你唯一节省的就是调用本身,代价是每次调用它时,编译器都必须将所有代码粘贴进去。如果你调用该代码 n 次,您的代码会按例程 * n 的大小增长。所以任何大于 10 行的东西都几乎不值得内联,除了只被调用很少次的特殊情况。这方面的一个例子可能是只有另外两个人调用的私有方法。

    如果您请求内联包含循环的方法,则只有当它经常执行少量时才有意义。但是考虑一个迭代一百万次的循环。即使代码是内联的,花费在调用中的时间百分比也很小。因此,如果您有带有循环的方法,无论如何它们往往会更大,那么这些方法值得从头文件中删除,因为它们 a) 往往会被编译器拒绝为内联,并且 b) 即使它们是内联的,通常也是不会提供任何好处

    【讨论】:

    • 你让我大吃一惊。如果我以正常方式定义一个函数(这里有一个定义,那里有一个实现),并且我将关键字 inline 放在前面(实现的?定义?两者?),那么编译器会考虑内联它吗?这更好吗?我对内联函数的主要担忧是它们很丑并且使行长。你是说我可以让我的正常性感看起来易于阅读的内联函数......不仅如此,而且我应该这样标记小函数?希望您是活跃用户并回复此评论。
    • 如果你使用原型定义一个函数,并且在同一个标​​有内联的头文件中实现,它将被内联。如果您的实现在另一个文件中,则编译器无法使其内联。它不是读心器,它不知道要生成什么代码。我个人不介意在标题中添加一些额外的代码,而不是写两次,但如果你愿意,你当然可以这样做。
    • @ziggy 如果我理解你的问题,是的,如果你的函数很短,那么在头文件中内联定义它可以大大提高运行时速度,同时稍微减慢编译时间和还需要定义内联函数中引用的任何符号。这意味着如果您的内联代码引用其他对象,则您的标头可能必须包含其他标头。因此,重新编译时间可能会付出很大的代价。但是对于一般的方法(getter/setter,用私有变量计算),内联是一个巨大的胜利。
    【解决方案3】:

    编译器必须将其视为内联请求——它可以忽略。在头文件中定义一些函数(例如空的虚拟析构函数)和一些必要的头文件定义(模板函数)有一些习惯用法,但除此之外,请参阅GotW #33 了解更多信息。

    有些人注意到编译器甚至可能内联您从未要求过的函数,但我不确定这是否会破坏请求内联函数的目的。

    【讨论】:

    • 取决于编译器。一般来说,内联是一个没有多大作用的提示。我认为大多数针对嵌入式平台的编译器都会更仔细地听取提示,而这确实是经过精心设计后您可能会超越编译器的唯一空间。
    【解决方案4】:

    它确实是内联的——但是编译器可以忽略任何内联请求。

    【讨论】:

      【解决方案5】:

      这是对编译器的请求,它可以忽略。

      【讨论】:

      • 不,这是一个要求。如果函数在类体中定义,则必须将其视为声明为inline
      • 就像编译器可以忽略的任何inline 请求一样。
      • 因为它可以被忽略,并且大多数编译器会在适当的地方内联,即使你不请求它,这是一个相当无用的提示。
      • @shoosh:无论是隐式还是显式,编译器都不能忽略内联函数的规则。例如,它们可能在多个翻译单元中合法定义。
      • @marr75:这不是没用的;它允许您在多个编译单元中定义函数,这对于内联它非常必要。它只是没有做您似乎希望它做的事情(即强制内联函数)。
      【解决方案6】:

      2003 ISO C++ 标准说

      7.1.2/2 带有内联的函数声明(8.3.5、9.3、11.4) 说明符声明内联 功能。内联说明符 向实现表明 函数的内联替换 呼叫点的身体是 优于通常的函数调用
      机制。一个实现不是 需要执行此内联
      在呼叫点替换; 但是,即使这个内联
      替换被省略,其他 定义内联函数的规则 到 7.1.2 仍应得到尊重。

      7.1.2/3 在类定义中定义的函数是内联函数
      功能。内联说明符应 没有出现在块作用域函数上 声明。

      7.1.2/4 应在
      中的每个翻译单元中定义内联函数 它被使用并应具有 每个中的定义完全相同
      案例(3.2)。 [注:调用 可能会遇到内联函数
      在其定义出现之前 翻译单元。 ] 如果一个函数 声明了带有外部链接的 内联在一个翻译单元中,它 应在所有声明中内联 它所在的翻译单位 出现;不需要诊断。一个 带外部的内联函数 链接应具有相同的地址 所有翻译单元。一个静态的 外部内联中的局部变量
      函数总是指同一个 目的。 an
      中的字符串文字 extern 内联函数是一样的 不同翻译的对象
      单位。

      【讨论】:

      • 这是最有用的答案,但也具有误导性,一般来说,主流编译器关注标准的“不需要实现来执行此内联替换”元素。
      【解决方案7】:

      有两件事不应该混为一谈:

      1. 如何将函数标记为内联:在签名前用内联定义它或在声明点定义它;
      2. 编译器将如何处理此类内联标记:无论您如何将函数标记为内联,它都将被编译器视为请求。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-07
        • 2019-03-24
        • 2017-01-05
        • 2011-08-23
        • 1970-01-01
        相关资源
        最近更新 更多