.cpp文件中的C ++内联成员函数


76

我知道,按定义,内联成员函数应该放在标题中。但是,如果不可能将函数的实现放到标题中怎么办?让我们来考虑这种情况:

文件啊

#pragma once
#include "B.h"

class A{
    B b;
};

文件Bh

#pragma once

class A; //forward declaration

class B{
    inline A getA();
};

由于包含了通知,我不得不将实施getA纳入

丙型肝炎

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

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

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


您是否有错误的印象,并认为关键字inline意味着编译器生成内联汇编而不是进行函数调用?
马丁·约克

罗-我知道会发生什么
马太福音

你确定吗。您认为应该怎么办?
马丁·约克

3
@垫。不。就内联代码而言,它毫无意义。这只是一个提示(所有现代编译器都忽略了)。编译器无需执行任何操作。编译器会分析所有函数以潜在地内联是否使用关键字“ inline”。
马丁·约克

3
是的,我知道这只是一个提示-无论如何,您说关键字“ inline”已经过时了吗?

Answers:


74

引用C ++常见问题解答

注意:除非仅在单个.cpp文件中使用函数,否则必须将函数的定义({...}之间的部分)放置在头文件中。特别是,如果将内联函数的定义放入.cpp文件中,并从其他一些.cpp文件中调用它,则会从链接器中收到“无法解析的外部”错误。

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

编译器会内联getA吗?

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

如果是这样,哪个内联关键字是有效的关键字(标头中的一个或cpp中的一个)?

最佳实践:仅在类主体之外进行定义。

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

不,至少我不知道。


21

不能,超出B.cpp的范围。编译器在每个编译单元的基础上运行,即,它单独编译每个.cpp文件,因此,如果编译C.cpp,它将没有可用的getA()代码,并且需要执行函数调用和让链接器对其进行修复(或者,如果确实让您误解并尝试内联,则最终将出现链接器错误。inline其性质与相似static)。

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

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

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


所以-在我的示例中-我只是将B.cpp重命名为B.inl,而我通常会在其中包含Bh的每个地方都改为包含B.inl?

1
我不会将B.cpp重命名为B.inl-我将创建一个单独的B.inl(或者最好是B_inline.h),并将所有内联函数放入其中。然后,在您的B.cpp中,包含_inline.h文件。
EboMike 2010年

1
“它不能在B.cpp的范围之外...” -由于某些工具链具有链接时间优化(LTO),因此NO的某些原因可能已经改变。-flto使用GCC和某些其他编译器时可以启用它。
jww

@jww不知道您是否读过第二段,但这就是我提到LTCG / LTO的地方:)现在,这当然比2010年写这篇文章时更普遍。尽管我个人仍然喜欢编写C ++代码,通过将可内联函数放入头文件来帮助编译器进行优化。
EboMike

8

我会从相反的方向去做。

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

唯一需要将内联声明添加到函数/方法的时间是,如果您在头文件中但在类声明之外定义了该函数。

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.

压缩文件

 // Never declare anything inline in the cpp file.

 int X::getZ() { return 6; }

给你更具体的情况。
删除所有内联规范。他们没有按照您认为的去做。


1
啊-如果在类的主体中定义了一个函数,那么无论如何都会内联它?

1
@Mat:将被标记为好像已应用inline关键字。不要将此与编译器内联代码混淆。所有现代的编译器都完全忽略该标签(对于pedantic而言:链接程序确实会使用它(但不是用于内联))。
马丁·约克

7

如今,大多数编译器可以在链接时以及编译时执行内联。如果您的函数可能会受益于内联,那么链接时间优化器可能会做到这一点。

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


我不知道那是可能的。链接程序是否关心“ inline”关键字,还是只是自行决定要内联什么?
马太福音

2
我不确定“最”部分。另外,LTCG的运行速度非常慢,我个人更喜欢对编译器的操作进行更多控制。
EboMike 2010年

1
@EboMike:您认为自己拥有的控制权只是一种幻想。在确定值得内联的内容时,编译器将永远比您更好。(人类进行所需的分析非常可怕)。让编译器完成它打算做的工作(内联),让人类去做自己擅长的事情(思考算法)。
马丁·约克

1
@Mat:所有现代编译器都忽略inline关键字(与内联代码有关)(除非您也用编译器标志强制它们,并且这总是一个错误(除非您真的知道自己在做什么,并且如果您认为自己在做)那么你就没有))。链接器确实需要inline关键字才能知道它应该忽略多个定义(或合并一个函数的多个定义),否则必须假定您搞砸了并生成错误。
马丁·约克

0

这是我的方法。

文件啊

#pragma once
#include "B.h"

class A {
    B b;
};

文件Bh

#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>();

文件通道

#pragma once
#include "A.h"
#include "B.h"

// Implement template specialization here!
template<> inline A B::getA<A>() { return A(); }

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

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

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.