为什么标题中的C ++内联函数?

Why are C++ inline functions in the header?

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

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

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

那么,为什么类函数的内联实现必须在头文件中呢?为什么不能将内联函数放入.cpp文件?如果我尝试将内联定义放在.cpp文件中的哪个位置,我会得到一个错误,沿着以下行:

1
2
3
4
5
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


inline函数的定义不必在头文件中,但由于内联函数只有一个定义规则,因此在使用该函数的每个翻译单元中都必须存在该函数的相同定义。

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

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

无论您是否应该声明一个函数inline,通常都是您应该根据一个定义规则的哪个版本做出的一个选择,它对您来说最有意义;添加inline,然后受随后的约束约束是没有意义的。


有两种方法来看待它:

  • 内联函数在头中声明,因为为了内联函数调用,编译器必须能够看到函数体。对于幼稚的编译器,函数体必须与调用位于同一翻译单元中。(现代编译器可以跨翻译单元进行优化,因此即使函数定义在单独的翻译单元中,函数调用也可能是内联的,但这些优化代价高昂,并不总是启用的,而且编译器并不总是支持这些优化)

  • 头中声明的函数必须标记为inline,因为否则,包括头的每个翻译单元都将包含函数的定义,链接器将投诉多个定义(违反了一个定义规则)。inline关键字抑制了这一点,允许多个翻译单元包含(相同的)定义。

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

    C++编译器可以自由地应用内联优化(用调用函数的函数替换函数调用,节省调用开销),只要它不改变程序的可观察行为。

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


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


    C++ EDCOX1的0个关键字是误导性的,并不意味着"内联这个函数"。如果一个函数被定义为inline,那么它只意味着只要所有定义都相等,它就可以被定义多次。标记为inline的函数是一个被调用的实际函数,而不是在调用时使代码内联,这是完全合法的。

    模板需要在头文件中定义一个函数,因为模板化的类实际上不是一个类,所以它是一个类的模板,您可以对其进行多种变体。为了使编译器能够在使用foo模板创建foo类时生成Foo::bar()函数,Foo::bar()的实际定义必须可见。


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

    请记住,C和C++使用一个非常简单的编译模型,编译器总是一次只看到一个翻译单元。(这对于出口来说是失败的,这是只有一个供应商实际执行的主要原因。)


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

    帮助者

    1
    2
    3
    4
    namespace DX
    {
        extern inline void ThrowIfFailed(HRESULT hr);
    }

    Help.CPP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    namespace DX
    {
        inline void ThrowIfFailed(HRESULT hr)
        {
            if (FAILED(hr))
            {
                std::stringstream ss;
                ss <<"#" << hr;
                throw std::exception(ss.str().c_str());
            }
        }
    }


    因为编译器需要看到它们以便内联它们。头文件是通常包含在其他翻译单元中的"组件"。

    1
    2
    3
    #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.

    内联函数

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

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

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

    在某些情况下,函数的内联无法工作,例如

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

    • 如果函数很复杂。

    • 如果函数的递归调用

    • 如果函数的地址是含蓄的或明确的

    下面在类外部定义的函数可能成为内联函数

    1
    2
    3
    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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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函数都将成为内联函数