首页 文章

为什么 Headers 中的C内联函数?

提问于
浏览
93

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

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

  • 89

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

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

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

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

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

  • 2

    有两种方法可以看待它:

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

    标头中声明的

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

    这两个解释实际上归结为 inline 关键字没有预期的事实 .

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

    通过允许函数定义在多个转换单元中可见, inline 关键字使编译器更容易应用此优化,但使用关键字并不意味着编译器必须内联函数,而不是使用关键字doesn' t禁止编译器内联函数 .

  • 1

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

  • 95

    原因是编译器必须实际看到 definition 才能将其丢弃以代替调用 .

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

  • 0

    c inline 关键字具有误导性,对于标记为 inline 的函数来说,它是一个完全合法的函数,它被调用而不是在被调用的位置获取内联代码 .

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

  • 19

    因为编译器需要看到它们才能 inline . 头文件是"components",它们通常包含在其他翻译单元中 .

    #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

    Inline Functions

    在C Micro中只有内联功能 . 现在微处理器都在编译器的控制之下 .

    • Important :如果我们在类中定义一个函数,它将自动变为 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函数都将成为内联函数

相关问题