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

时间:2011-02-20 12:28:24

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

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

8 个答案:

答案 0 :(得分:99)

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

实现此目的的最简单方法是将定义放在头文件中。

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

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

答案 1 :(得分:97)

有两种方法可以看待它:

  1. 内联函数在标头中声明,因为为了内联函数调用,编译器必须能够看到函数体。对于一个天真的编译器来说,函数体必须与调用在同一个转换单元中。 (现代编译器可以跨翻译单元进行优化,因此即使函数定义位于单独的转换单元中,也可以内联函数调用,但这些优化很昂贵,并不总是启用,并且并不总是支持编译器)

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

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

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

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

答案 2 :(得分:20)

这是C ++编译器的限制。如果将函数放在标题中,那么可以在其中内联的所有cpp文件都可以看到函数的“源”,并且内联可以由编译器完成。其他内联必须由链接器完成(每个cpp文件分别在obj文件中编译)。问题是在链接器中执行它会困难得多。 “模板”类/函数存在类似的问题。它们需要由编译器实例化,因为链接器将有问题实例化(创建它们的专用版本)。一些较新的编译器/链接器可以执行“双通”编译/链接,其中编译器执行第一次传递,然后链接器执行其工作并调用编译器来解析未解析的事物(内联/模板...)

答案 3 :(得分:8)

原因是编译器必须实际看到 定义 才能将其删除以代替调用。

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

答案 4 :(得分:8)

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

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

答案 5 :(得分:3)

我知道这是一个旧帖子,但我想我应该提一下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());
        }
    }
}

答案 6 :(得分:1)

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

#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.

答案 7 :(得分:0)

内联函数

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

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

Inline函数的代码在被调用的位置被替换,从而减少了调用函数的开销。

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

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

  • 如果函数很复杂。

  • 如果函数的递归调用

  • 是隐式还是显式获取函数的地址

在类外部定义的函数可能会变成内联

np.multiply

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

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

getSpeed和setSpeed函数都将内联