ChatGPT解决这个技术问题 Extra ChatGPT

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

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

类成员函数的声明不需要将函数定义为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我会说第二个链接是相似的,但我更多地询问为什么内联工作方式背后的逻辑。
在这种情况下,我认为您可能误解了“内联”或“头文件”;你的两个断言都不正确。您可以拥有成员函数的内联实现,并且可以将内联函数定义放在头文件中,但这可能不是一个好主意。你能澄清你的问题吗?
编辑后,我想您可能会询问 inline 出现在定义中而不是先前声明与 反之亦然的情况。如果是这样,这可能会有所帮助:stackoverflow.com/questions/4924912/…

E
Enlico

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

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

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

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


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

有两种查看方式:

内联函数在头文件中定义,因为为了内联函数调用,编译器必须能够看到函数体。对于一个简单的编译器来说,函数体必须与调用在同一个翻译单元中。 (现代编译器可以跨翻译单元进行优化,因此即使函数定义在单独的翻译单元中,也可以内联函数调用,但这些优化代价高昂,并不总是启用,并且并不总是被头文件中定义的函数必须标记为内联,否则,包含头文件的每个翻译单元都将包含函数的定义,并且链接器将抱怨多个定义(违反单一定义规则)。 inline 关键字抑制了这一点,允许多个翻译单元包含(相同的)定义。

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

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

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


x
xanatos

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


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

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

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


而且既然是类的模板,就不叫模板类,而是类模板。
第一段是完全正确的(我希望我可以强调“误导”),但我认为不需要在模板中添加不合逻辑的内容。
一些编译器会使用它作为函数可以可能被内联的提示,但事实上,它并不能保证仅仅因为你声明它inline就被内联(也不声明它{1 } 保证它不会被内联)。
这是最好的描述,但我同意@ThomasEdleson 的观点,即 segue 没有帮助,实际上是让我无法投票的原因。
s
sbi

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

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


f
flamewave000

我知道这是一个旧线程,但我认为我应该提到 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());
        }
    }
}

除非您使用全程序优化 (WPO),否则这通常不会导致函数实际被内联。
L
Leandro T. C. Melo

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

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

L
Louis Cloete

内联函数

在 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 函数基本算术。
嘿,感谢您的评论...以下是 Thinking in C++ micc.unifi.it/bertini/download/programmazione/… Page 400 中的几行 .. 请检查 .. 如果您同意,请投票。谢谢.....类内的内联 要定义内联函数,您通常必须在函数定义之前加上 inline 关键字。但是,这在类定义中不是必需的。您在类定义中定义的任何函数都会自动成为内联函数。
那本书的作者可以声称他们想要什么,因为他们写的是书而不是代码。这是我必须深入分析的内容,以便通过尽可能避免内联代码使我的便携式 3d 演示适合小于 64kb 的大小。编程是关于事实而不是宗教,所以如果某个“神程序员”在书中说它不能代表实践中发生的事情,那也没关系。并且大多数 C++ 书籍都收集了一些不好的建议,有时您会在其中找到一些巧妙的技巧来添加到您的目录中。
嘿@PabloAriel谢谢...请分析并告诉我..我可以根据分析更新这个答案