【问题标题】:C++ inline member function in .cpp file.cpp 文件中的 C++ 内联成员函数
【发布时间】:2011-04-28 22:10:06
【问题描述】:

我知道根据定义,内联成员函数应该放在标题中。但是,如果不可能将函数的实现放入头文件中怎么办?让我们来看看这种情况:

文件 A.h

#pragma once
#include "B.h"

class A{
    B b;
};

文件 B.h

#pragma once

class A; //forward declaration

class B{
    inline A getA();
};

由于包含通知,我必须将getA 的实现放入

B.cpp

#include "B.h"
#include "A.h"

inline A B::getA(){
    return A();
}

编译器会内联getA吗?如果是这样,哪个内联关键字是重要的(标题中的关键字或 .cpp 文件中的关键字)?是否有另一种方法可以将内联成员函数的定义放入其 .cpp 文件中?

【问题讨论】:

  • 你是不是有错误的印象,认为关键字inline的意思是编译器生成内联汇编而不是进行函数调用?
  • 不-我知道应该发生什么
  • 你确定。你认为应该发生什么?
  • @Mat.不。就内联代码而言,这没有任何意义。这只是一个提示(所有现代编译器都忽略了)。编译器不需要做任何事情。无论是否使用关键字“inline”,编译器都会分析所有函数以进行潜在内联。
  • 是的,我知道这只是一个提示——反正你说关键字“inline”已经过时了?

标签: c++ function inline member


【解决方案1】:

引用C++ FAQ:

注意:必须 函数的定义(部分 {...}) 之间被放置在一个 头文件,除非函数是 仅在单个 .cpp 文件中使用。在 特别是,如果你把内联 函数的定义到 .cpp 文件中 你从其他一些 .cpp 调用它 文件,你会得到一个“未解决 来自链接器的“外部”错误。

当编译器发现内联函数有任何用途时,它需要查看内联函数的定义。如果将内联函数放在头文件中,这通常是可能的。

编译器会内联getA吗?

不,除非 getA() 的使用在 B.cpp 本身中。

如果是这样,哪个内联关键字是重要的(标题中的关键字或 cpp 中的关键字)?

Best practice: 只在类体之外的定义中。

还有其他方法可以将内联成员函数的定义放入它的 cpp 文件中吗?

不,至少我不知道。

【讨论】:

    【解决方案2】:

    它不能,超出 B.cpp 的范围。编译器在每个编译单元的基础上运行,即它单独编译每个 .cpp 文件,因此如果它编译 C.cpp,它将没有可用的 getA() 代码,并且需要执行函数调用和让链接器修复它(或者,如果它真的按字面意思尝试内联,它最终会出现链接器错误。inlinestatic 具有相似的品质)。

    唯一的例外是 LTCG,即链接时代码生成,它在较新的编译器上可用。

    在这种情况下,一种方法是使用另一个包含内联代码的头文件(有时称为 *.inl 文件)。

    编辑:至于哪个内联是相关的 - 它是类定义中的一个,即在头文件中。请记住,许多编译器对可以内联和应该内联的内容有自己的想法。例如 gcc 可以完全禁用内联(-O0),或者它可以内联它认为值得内联的任何内容(如 -O3)。

    【讨论】:

    • 所以 - 在我的示例中 - 我只需将 B.cpp 重命名为 B.inl 并且我通常会包含 B.h 的每个地方都包含 B.inl?
    • 我不会将 B.cpp 重命名为 B.inl - 我会创建一个单独的 B.inl(或者最好是 B_inline.h)并将所有内联函数放在那里。然后,在您的 B.cpp 中,包含 _inline.h 文件。
    • “它不能,在 B.cpp 的范围之外......” - 现在一些工具链有链接时间,NO 的一些原因可能已经改变优化 (LTO)。在使用 GCC 和其他一些编译器时可以启用-flto
    • @jww 不确定你是否读到了第二段,但这就是我提到 LTCG/LTO 的地方 :) 这当然比 2010 年我写这篇文章时更常见。虽然我个人仍然喜欢编写 C++ 代码,通过将可内联函数放入头文件中来帮助编译器优化。
    【解决方案3】:

    我会从相反的方向着手。

    不要在你的函数中添加内联声明(除非你也需要)。

    您需要将内联声明添加到函数/方法的唯一情况是您在头文件中但在类声明之外定义函数。

    X.h

    class X
    {
        public:
            int getX()   { return 4;} // No inline because it is part of the class.
                                      // The compiler knows that needs an inline tag
            int getY();
            int getZ();
    };
    
    inline
    int X::getY()  { return 5;}       // This needs to be explicitly declared inline.
                                      // Otherwise the linker will complain about
                                      // multiple definitions in compilation units.
    

    X.cpp

     // Never declare anything inline in the cpp file.
    
     int X::getZ() { return 6; }
    

    给你更具体的情况。
    删除所有内联规范。他们没有做你认为他们正在做的事情。

    【讨论】:

    • 啊 - 所以如果一个函数是在类的主体中定义的,那么它无论如何都会被内联?
    • @Mat:它将被标记为好像应用了 inline 关键字。不要将此与编译器内联代码混淆。所有现代编译器都完全忽略该标签(对于迂腐的:链接器确实使用它(但不是用于内联)。
    【解决方案4】:

    如今,大多数编译器都可以在链接时和编译时执行内联。如果您的函数可能会从内联中受益,那么链接时间优化器很可能会这样做。

    当链接器到达它时,编译器输出的内联状态并不多,除了编译器会将某些对象标记为可收集,例如因为内联函数或类模板实例出现在多次编译中单位,或者当多个符号共享一个名称时应该引发错误,例如主函数被定义两次。这些都不会影响它将生成的实际代码。

    【讨论】:

    • 我不知道这是可能的。链接器是否关心“内联”关键字,或者它只是自己决定内联什么?
    • 我不太确定“最”部分。此外,LTCG 速度非常慢,我个人更喜欢对编译器的功能进行更多控制。
    • @EboMike:您认为自己拥有的控制权是一种幻觉。在决定什么值得内联时,编译器总是比你好。 (人类在进行所需的分析方面很糟糕)。让编译器做它应该做的工作(内联)让人类做它擅长的事情(考虑算法)。
    • @Mat:所有现代编译器都忽略了 inline 关键字(与内联代码有关)(除非你也用编译器标志强制它们,这总是一个错误(除非你真的知道你正在做,如果你认为你做了,那么你不做))。链接器确实需要 inline 关键字来知道它应该忽略多个定义(或合并函数的多个定义),否则它必须假定您搞砸了并产生错误。
    【解决方案5】:

    这是我的做法。

    文件 A.h

    #pragma once
    #include "B.h"
    
    class A {
        B b;
    };
    

    文件 B.h

    #pragma once
    
    class B {
    public:
        template<class T> inline T getA() {
            assert(NULL); // Use 'getA<A>()' template specialization only!
            return NULL;
        }
    };
    
    class A; // Forward declaration
    template<> inline A B::getA<A>();
    

    文件 C.h

    #pragma once
    #include "A.h"
    #include "B.h"
    
    // Implement template specialization here!
    template<> inline A B::getA<A>() { return A(); }
    

    只需包含“C.h”文件即可使用 getA() 方法。与原始代码的唯一变化是 getA() 方法必须定义为 public 而不是 private。

    但是,正如你们中的许多人所解释的那样,这并不是真的有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-27
      • 2023-03-02
      • 1970-01-01
      • 2012-01-17
      相关资源
      最近更新 更多