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

the*_*man 109 c++ theory language-design inline c++-faq

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

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

struct foo{
    void bar(); // no need to define this as inline
}
Run Code Online (Sandbox Code Playgroud)

那么,为什么一类功能的内嵌实施是在头文件?为什么我不能把内联函数放在.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
Run Code Online (Sandbox Code Playgroud)

CB *_*ley 110

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

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

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

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

  • 但是编译器不会编译包含 .h 文件的 .cpp 文件吗...这样,当它编译 .cpp 文件时,它就会同时包含减速和源文件。引入的其他头文件只是它们的,因此编译器可以“相信”这些函数确实存在并将在其他源文件中实现 (2认同)
  • @thecoshman:有两个区别.源文件vs头文件.按照惯例,头文件通常是指源文件,它不是翻译单元的基础,但仅包含在其他源文件中的#included.然后是声明与定义.您可以在头文件或"普通"源文件中声明函数的声明或定义.恐怕我不确定你在评论中提到的是什么. (2认同)

jal*_*alf 106

有两种方法可以看待它:

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

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

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

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

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


xan*_*tos 20

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

  • 我不同意这个答案,它不是C++编译器的限制; 纯粹是如何指定语言规则.语言规则允许简单的编译模型,但它们不禁止替代实现. (10认同)
  • 虽然这个答案似乎确实存在一些技术错误,但它确实帮助我了解编译器如何使用头文件等. (4认同)
  • 我同意@Charles.事实上,有些编译器可以跨翻译单元内联函数,所以这绝对不是由于编译器的限制. (3认同)

sbi*_*sbi 10

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

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


Eri*_*rik 9

c ++ inline关键字具有误导性,它并不意味着"内联此函数".如果函数定义为内联,则只是意味着只要所有定义相等,就可以定义多次.对于标记inline为真实函数的函数来说,这是完全合法的,而不是在被调用的位置获取内联代码.

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

  • 第一段是完全正确的(我希望我可以强调"误导"),但我认为不需要将非sequitur转换为模板. (3认同)
  • 由于它是_类的模板_,所以它不称为_模板类_,而是_类模板_。 (2认同)

Lea*_*elo 5

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

#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.
Run Code Online (Sandbox Code Playgroud)


fla*_*000 5

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

助手.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}
Run Code Online (Sandbox Code Playgroud)

助手.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 除非您使用全程序优化 (WPO),否则这通常不会导致函数实际被内联。 (9认同)