为什么标头中包含C ++内联函数?


120

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

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



@Charles我会说第二个链接是相似的,但是我想问更多有关内联为什么以这种方式工作的逻辑。
thecoshman 2011年

2
在这种情况下,我认为您可能误解了“内联”或“头文件”;您的断言都不对。您可以对成员函数进行内联实现,也可以将内联函数定义放在头文件中,但这可能不是一个好主意。你能澄清你的问题吗?
CB Bailey

编辑后,我想您可能会问有关inline出现在定义上而不是先于声明的情况,反之亦然。如果是这样,这可能会帮助:stackoverflow.com/questions/4924912/...
CB贝利

Answers:


122

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

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

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

inline通常,应该根据一个定义规则的哪个版本来选择是否声明一个函数,这对您来说最有意义。添加inline,然后受后续约束的约束几乎没有任何意义。


但是编译器不会编译包含.h文件的.cpp文件...,以便在编译.cpp文件时,它既具有减速度,又具有源文件。其他的头文件只是它们的文件,因此编译器可以“相信”这些功能的确存在,并将在其他源文件中实现
thecoshman 2011年

1
对我来说,这实际上是比我更好的答案+1
2011年

2
@thecoshman:有两个区别。源文件与头文件。按照惯例,头文件通常是指不是翻译单元基础的源文件,而仅包含在其他源文件中。然后是声明与定义。您可以在头文件或“常规”源文件中具有函数的声明或定义。恐怕我不确定您在评论中要问什么。
CB Bailey

不用担心,我知道为什么会这样...尽管我不确定是谁真正回答了这个问题。您和@Xanatos的答案共同为我解释了这一点。
thecoshman 2011年

113

有两种查看方式:

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

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

这两种解释确实可以归结为以下事实:inline关键字不能完全满足您的期望。

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

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


23

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


哦,我明白了!是的,不是针对类本身,而是使用内联函数,其他使用内联函数的代码。他们只看到要内联的类的头文件!
thecoshman 2011年

11
我不同意这个答案,这不是C ++编译器的限制。纯粹是指定语言规则的方式。语言规则允许使用简单的编译模型,但不禁止其他实现。
CB Bailey

3
我同意@Charles。实际上,有些编译器可以跨翻译单元内联函数,因此,这绝对不是由于编译器的限制。
2011年

5
虽然此答案似乎确实存在一些技术错误,但确实帮助我了解了编译器如何处理头文件等。
thecoshman 2011年

10

原因是编译器必须实际看到该定义才能将其替换为调用。

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


9

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

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


并且因为它是一个模板,所以它不称为模板类,而是一个类模板
2011年

4
第一段是完全正确的(我希望我能强调“误导性”),但是我认为没有必要将非必填项纳入模板中。
Thomas Edleson

一些编译器将其用作暗示该函数可能会内联的提示,但实际上,不能仅因为声明了它就保证内联该函数inline(也不声明它inline不会内联)。
基思M

4

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

助手

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
除非您使用整体程序优化(WPO),否则通常不会导致实际上内联函数。
Chuck Walbourn 2014年

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.

1

内联函数

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

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

内联函数的代码在被调用的位置被替换,因此减少了调用函数的开销。

在某些情况下,内联功能无法正常工作,例如

  • 内联函数内部是否使用了静态变量。

  • 如果功能复杂。

  • 如果递归调用函数

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

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

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


嗯,也许有一些不错的信息,但是并没有真正尝试解释为什么。也许您会做,但是并不清楚。
thecoshman

2
以下语句不正确:“重要提示:如果我们在类内部定义一个函数,它将自动变为Inline”。即使您在声明/定义中编写“ inline”,也可以确保它实际上是内联的。甚至没有模板。也许您的意思是编译器会自动采用“ inline”关键字,但不必遵循该关键字,而我注意到的是,在大多数情况下,即使对于简单的constexpr函数,它也不会内联此类标题定义基本算术。
Pablo Ariel

嘿,谢谢您的评论...下面是C ++中的Thinking行micc.unifi.it/bertini/download/programmazione/… 页400 ..请检查..如果您同意,请点。谢谢.....类中的内联要定义内联函数,通常必须在函数定义之前添加inline关键字。但是,在类定义中这不是必需的。您在类定义中定义的任何函数都会自动成为内联函数。
莎拉布

该书的作者可以声称自己想要什么,因为他们是写书而不是编写代码。我必须对此进行深入分析,以便通过尽可能避免内联代码来使我的便携式3d演示文件的大小小于64kb。编程是关于事实的,而不是宗教的,因此,如果某个“上帝程序员”在书中说过它并不能代表实践中发生的事情,那实际上并不重要。而且,大多数C ++书籍中都有很多不好的建议,在这些建议中,您会不时找到一些巧妙的技巧来添加您的曲目。
Pablo Ariel

嘿@PabloAriel谢谢...请分析,让我知道..根据分析,我可以更新此答案
Saurabh Raoot
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.