【问题标题】:Why are C++ inline functions in the header?为什么标头中有 C++ 内联函数?
【发布时间】:2011-06-30 17:33:35
【问题描述】:

NB这不是关于如何使用内联函数或它们如何工作的问题,而是关于它们为什么以它们的方式完成的问题。

类成员函数的声明不需要将函数定义为inline,它只是函数的实际实现。例如在头文件中:

struct foo{
    void bar(); // no need to define this as inline
}

那么为什么类函数的内联实现必须在头文件中呢?为什么我不能将内联函数放在.cpp 文件中?如果我尝试将内联定义放在 .cpp 文件中,我会收到如下错误:

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals

【问题讨论】:

标签: c++ inline theory language-design c++-faq


【解决方案1】:

inline 函数的定义不必在头文件中,但由于内联函数的定义规则 (ODR),对于函数必须存在于每个使用它的翻译单元中。

实现这一点的最简单方法是将定义放在头文件中。

如果您想将函数的定义放在单个源文件中,则不应将其声明为inline。未声明的函数inline 并不意味着编译器不能内联该函数。

您是否应该声明一个函数inline 通常是您应该根据一个定义规则的哪个版本做出的选择,这对您来说最有意义;添加inline,然后受到后续约束的限制意义不大。

【讨论】:

  • 但是编译器不会编译包含 .h 文件的 .cpp 文件...所以当它编译 .cpp 文件时它既有减速也有源文件.引入的其他头文件只是它们的,因此编译器可以“相信”这些函数确实存在并且将在其他一些源文件中实现
  • 这实际上是比我更好的答案,+1 来自我!
  • @thecoshman:有两个区别。源文件与头文件。按照惯例,头文件通常是指不是翻译单元的基础的源文件,而是仅#included 来自其他源文件。然后是声明与定义。您可以在头文件或“普通”源文件中声明或定义函数。恐怕我不确定您在评论中要问什么。
  • 别担心,我明白为什么会是现在......虽然我不确定谁真正回答了这个问题。您和@Xanatos 的回答相结合为我解释了这一点。
【解决方案2】:

有两种查看方式:

  1. 内联函数在头文件中定义,因为为了内联函数调用,编译器必须能够看到函数体。对于一个简单的编译器来说,函数体必须与调用在同一个翻译单元中。 (现代编译器可以跨翻译单元进行优化,因此即使函数定义在单独的翻译单元中,也可以内联函数调用,但这些优化代价高昂,并不总是启用,并且并不总是被编译器)

  2. 标头中定义的函数必须标记为inline,否则,包含标头的每个翻译单元都将包含该函数的定义,并且链接器将抱怨多个定义(违反单一定义规则)。 inline 关键字抑制了这一点,允许多个翻译单元包含(相同的)定义。

这两种解释实际上归结为 inline 关键字并不完全符合您的预期。

C++ 编译器可以随时应用内联优化(用被调用函数的主体替换函数调用,节省调用开销),只要它不这样做改变程序的可观察行为。

inline 关键字通过允许函数定义在多个翻译单元中可见,使编译器更容易应用此优化,但使用关键字并不意味着编译器 has 内联函数,而 not 使用关键字并不禁止编译器内联函数。

【讨论】:

    【解决方案3】:

    这是 C++ 编译器的限制。如果你把函数放在头文件中,所有可以内联的cpp文件都可以看到你的函数的“源代码”,并且内联可以由编译器完成。否则内联必须由链接器完成(每个 cpp 文件分别编译在一个 obj 文件中)。问题是在链接器中执行此操作要困难得多。 “模板”类/函数也存在类似问题。它们需要由编译器实例化,因为链接器在实例化(创建专用版本)它们时会遇到问题。一些较新的编译器/链接器可以执行“两遍”编译/链接,其中编译器执行第一遍,然后链接器完成其工作并调用编译器来解决未解决的问题(内联/模板...)

    【讨论】:

    • 哦,我明白了!是的,它不是因为它自己使用内联函数的类,而是它使用内联函数的其他代码。他们只看到被内联的类的头文件!
    • 我不同意这个答案,这不是 C++ 编译器的限制;这纯粹是语言规则的指定方式。语言规则允许使用简单的编译模型,但不禁止替代实现。
    • 我同意@Charles。事实上,有些编译器会跨翻译单元内联函数,所以这绝对不是由于编译器的限制。
    • 虽然这个答案似乎确实存在一些技术错误,但它确实帮助我了解了编译器如何处理头文件等。
    【解决方案4】:

    c++ inline 关键字具有误导性,并不意味着“内联此函数”。如果一个函数被定义为内联,它仅仅意味着它可以被定义多次,只要所有定义都相等。将标记为 inline 的函数作为真正的函数被调用而不是在调用点内联代码是完全合法的。

    模板需要在头文件中定义函数,因为例如模板类并不是真正的类,它是一个类的 模板,您可以对其进行多种变体。为了使编译器能够例如制作Foo<int>::bar()函数当你使用Foo模板创建Foo类时Foo<T>::bar()的实际定义必须是可见的。

    【讨论】:

    • 而且由于它是类的模板,所以不叫模板类,而是类模板 i>.
    • 第一段是完全正确的(我希望我可以强调“误导”),但我不认为需要在模板中添加不合理的内容。
    • 一些编译器会使用它作为函数可以可能被内联的提示,但事实上,并不能保证仅仅因为你声明它就被内联inline (也不声明它inline 保证它不会被内联)。
    【解决方案5】:

    原因是编译器必须实际看到 定义 才能将其放置在调用位置。

    请记住,C 和 C++ 使用非常简单的编译模型,编译器一次只能看到一个翻译单元。 (导出失败,这是只有一个供应商实际实施它的主要原因。)

    【讨论】:

      【解决方案6】:

      我知道这是一个旧线程,但我认为我应该提到 extern 关键字。我最近遇到了这个问题,解决如下

      Helper.h

      namespace DX
      {
          extern inline void ThrowIfFailed(HRESULT hr);
      }
      

      Helper.cpp

      namespace DX
      {
          inline void ThrowIfFailed(HRESULT hr)
          {
              if (FAILED(hr))
              {
                  std::stringstream ss;
                  ss << "#" << hr;
                  throw std::exception(ss.str().c_str());
              }
          }
      }
      

      【讨论】:

      • 这通常不会导致函数实际被内联,除非您使用全程序优化 (WPO)。
      【解决方案7】:

      因为编译器需要看到它们才能内联它们。头文件是其他翻译单元中通常包含的“组件”。

      #include "file.h"
      // Ok, now me (the compiler) can see the definition of that inline function. 
      // So I'm able to replace calls for the actual implementation.
      

      【讨论】:

        【解决方案8】:

        内联函数

        在 C++ 中,宏只不过是内联函数。所以现在宏在编译器的控制之下。

        • 重要:如果我们在类中定义一个函数,它将自动变为内联

        内联函数的代码在被调用的地方替换,减少了调用函数的开销。

        在某些情况下,函数的内联不起作用,例如

        • 如果在内联函数中使用了静态变量。

        • 如果功能复杂。

        • 如果递归调用函数

        • 如果函数的地址是隐式或显式的

        在类外定义的函数如下可能变为内联

        inline int AddTwoVar(int x,int y); //This may not become inline 
        
        inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline
        

        类内部定义的函数也变成内联函数

        // Inline SpeedMeter functions
        class SpeedMeter
        {
            int speed;
            public:
            int getSpeed() const { return speed; }
            void setSpeed(int varSpeed) { speed = varSpeed; }
        };
        int main()
        {
            SpeedMeter objSM;
            objSM.setSpeed(80);
            int speedValue = A.getSpeed();
        } 
        

        这里 getSpeed 和 setSpeed 函数都将变成内联函数

        【讨论】:

        • 嗯,也许有一些不错的信息,但并没有真正尝试解释为什么。也许你知道,但只是没有说清楚。
        • 以下陈述不正确:“重要:如果我们在类中定义一个函数,它将自动变为内联”即使您在声明/定义中写“内联”也不能确定它是实际上是内联的。甚至对于模板也不行。也许你的意思是编译器自动假定“内联”关键字,但不必遵循,我注意到在大多数情况下,它不会内联这样的头文件定义,即使是简单的 constexpr 函数基本算术。
        • 嘿,感谢 cmets ...以下是 Thinking in C++ micc.unifi.it/bertini/download/programmazione/… 第 400 页 .. 请检查 .. 如果您同意,请点赞。谢谢.....类内的内联 要定义内联函数,您通常必须在函数定义之前加上 inline 关键字。但是,这在类定义中不是必需的。您在类定义中定义的任何函数都会自动成为内联函数。
        • 那本书的作者可以声称他们想要什么,因为他们写的是书而不是代码。这是我必须深入分析的内容,以便通过尽可能避免内联代码来使我的便携式 3d 演示适合小于 64kb 的大小。编程是关于事实而不是宗教,所以如果某个“神级程序员”在书中说它不能代表实践中发生的事情,那么它并不重要。并且大多数 C++ 书籍都包含一系列糟糕的建议,您有时会从中找到一些巧妙的技巧来添加到您的目录中。
        • 嘿@PabloAriel 谢谢...请分析并告诉我..我可以根据分析更新此答案
        猜你喜欢
        • 1970-01-01
        • 2014-08-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多