143

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

类成员函数的声明不需要将函数定义为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
4

8 回答 8

144

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

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

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

您是否应该声明一个函数inline通常是您应该根据哪个版本的定义规则最适合您遵循的选择;添加inline然后受到后续约束的限制几乎没有意义。

于 2011-02-20T12:35:14.697 回答
131

有两种查看方式:

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

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

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

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

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

于 2011-02-20T13:01:15.210 回答
29

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

于 2011-02-20T12:30:49.477 回答
13

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

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

于 2011-02-20T12:35:41.443 回答
11

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

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

于 2011-02-20T12:35:17.347 回答
4

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

助手.h

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

助手.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}
于 2014-05-02T15:46:45.043 回答
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.
于 2011-02-20T12:29:25.297 回答
0

内联函数

在 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 函数都将变成内联函数

于 2018-08-10T12:13:23.093 回答