C++头文件如何包含实现?

Mar*_*eIV 59 c++ header-files

好吧,无论如何不是C/C++专家,但我认为头文件的目的是声明函数,然后C/CPP文件来定义实现.

但是,今晚回顾一些C++代码,我发现这是在类的头文件中...

public:
    UInt32 GetNumberChannels() const { return _numberChannels; } // <-- Huh??

private:
    UInt32 _numberChannels;
Run Code Online (Sandbox Code Playgroud)

那么为什么标题中有实现呢?是否与const关键字有关?这是内联类方法吗?与定义CPP文件中的实现相比,这样做的好处/意义究竟是什么?

Rem*_*eau 107

好吧,无论如何不是C/C++专家,但我认为头文件的目的是声明函数,然后C/CPP文件来定义实现.

头文件的真正目的是在多个源文件之间共享代码.它通常用于将声明与实现分开以实现更好的代码管理,但这不是必需的.可以编写不依赖头文件的代码,并且可以编写仅由头文件组成的代码(STL和Boost库就是很好的例子).请记住,当预处理器遇到#include语句时,它会将语句替换为所引用文件的内容,然后编译器只会看到已完成的预处理代码.

因此,例如,如果您有以下文件:

foo.h中:

#ifndef FooH
#define FooH

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

#endif
Run Code Online (Sandbox Code Playgroud)

Foo.cpp中:

#include "Foo.h"

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}
Run Code Online (Sandbox Code Playgroud)

Bar.cpp:

#include "Foo.h"

Foo f;
UInt32 chans = f.GetNumberChannels();
Run Code Online (Sandbox Code Playgroud)

预处理器解析Foo.cpp中和Bar.cpp分开,并产生如下代码,该编译器然后分析:

Foo.cpp中:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}
Run Code Online (Sandbox Code Playgroud)

Bar.cpp:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

Foo f;
UInt32 chans = f.GetNumberChannels();
Run Code Online (Sandbox Code Playgroud)

Bar.cpp编译成Bar.obj并包含对call的引用Foo::GetNumberChannels().Foo.cpp编译成Foo.obj并包含实际的实现Foo::GetNumberChannels().编译之后,链接器然后匹配.obj文件并将它们链接在一起以生成最终的可执行文件.

那么为什么标题中有实现呢?

通过在方法声明中包含方法实现,它被隐式声明为内联(有一个inline可以显式使用的实际关键字).指示编译器应该内联函数只是一个提示,它不能保证函数实际上会被内联.但如果确实如此,那么无论在哪里调用内联函数,函数的内容都会直接复制到调用站点,而不是生成一个CALL语句来跳转到函数并在退出时跳回调用者.然后,编译器可以考虑周围的代码,并在可能的情况下进一步优化复制的代码. 

是否与const关键字有关?

否.该const关键字仅向编译器指示该方法不会改变在运行时调用它的对象的状态.

与定义CPP文件中的实现相比,这样做的好处/意义究竟是什么?

有效使用时,它允许编译器通常生成更快,更优化的机器代码.

  • 根据您的解释,这是否意味着您可以直接在CPP文件中声明一个类,甚至在包装该类声明的大括号内声明成员函数的内容,因此您不必在外部使用::语法呢?(我知道这不是好的编码。我只是在问它是否是有效的编码。)从这个意义上讲,这是否意味着所有成员都将被内联,或者至少被标记为?(还有什么话可以说*不要*内联吗?) (2认同)

Age*_*ien 28

在头文件中实现函数是完全有效的.唯一的问题是打破单定义规则.也就是说,如果您包含来自多个其他文件的标头,则会出现编译器错误.

但是,有一个例外.如果声明函数是内联的,则它不受one-definition-rule的约束.这就是这里发生的事情,因为在类定义中定义的成员函数是隐式内联的.

内联本身是对编译器的暗示,函数可能是内联的良好候选者.也就是说,将对它的任何调用扩展为函数的定义,而不是简单的函数调用.这是一种优化,它可以交换生成的文件的大小以获得更快的代码.在现代编译器中,除了它对单一定义规则的影响之外,为函数提供这种内联提示大多被忽略.此外,编译器总是可以自由地内联它认为合适的任何函数,即使它尚未声明inline(显式或隐式).

在您的示例中,在const参数列表之后的使用表示成员函数不会修改调用它的对象.在实践中,这意味着this将考虑所有类成员所指向的对象,并且通过扩展所有类成员const.也就是说,尝试修改它们会产生编译时错误.

  • 感谢您提及[one-definition-rule](http://en.wikipedia.org/wiki/One_Definition_Rule)! (4认同)
  • “因为在类定义内定义的成员函数是隐式内联的。” 有价值的信息。不知道。但是那个“ const”字呢? (2认同)

jua*_*nza 5

它是隐式声明 inline由于是一个成员函数定义的类声明中。这并不意味着编译器必须内联它,而是意味着您不会违反一个定义规则。它与const*完全无关。它也与功能的长度和复杂性无关。

如果它是非成员函数,则必须将其显式声明为inline

inline void foo() { std::cout << "foo!\n"; }
Run Code Online (Sandbox Code Playgroud)

*有关成员函数结尾的更多信息,请参见此处const


ste*_*eha 5

即使在纯 C 语言中,也可以将代码放入头文件中。如果这样做,通常需要声明它static,否则包含相同标头的多个 .c 文件将导致“多重定义函数”错误。

预处理器以文本方式包含一个包含文件,因此包含文件中的代码成为源文件的一部分(至少从编译器的角度来看)。

C++ 的设计者希望能够实现具有良好数据隐藏的面向对象编程,因此他们期望看到大量的 getter 和 setter 函数。他们不希望受到不合理的性能惩罚。因此,他们设计了 C++,以便 getter 和 setter 不仅可以在标头中声明,而且可以实际实现,因此它们是内联的。你展示的那个函数是一个 getter,当 C++ 代码被编译时,不会有任何函数调用;取出该值的代码将被就地编译。

可以创建一种没有头文件/源文件区别的计算机语言,而只具有编译器可以理解的实际“模块”。(C++ 没有这样做;它们只是构建在源文件和文本包含的头文件的成功 C 模型之上。)如果源文件是模块,则编译器可以从模块中提取代码,然后内联该代码。但 C++ 的实现方式更简单。