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

时间:2021-08-21 12:33:46

Ok, not a C/C++ expert by any means, but I thought the point of a header file was to declare the functions, then the C/CPP file was to define the implementation.

好吧,无论如何都不是一个C/ c++专家,但是我认为头文件的要点是声明函数,然后C/CPP文件定义实现。

However, reviewing some C++ code tonight, I found this in a class's header file...

然而,在今晚回顾一些c++代码时,我在一个类的头文件中发现了这一点……

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

private:
    UInt32 _numberChannels;

So why is there an implementation in a header? Does it have to do with the const keyword? Does that inline a class method? What exactly is the benefit/point of doing it this way vs. defining the implementation in the CPP file?

那么为什么头中有实现呢?这和const关键字有关吗?这是内联类方法吗?这样做与在CPP文件中定义实现究竟有什么好处/意义?

6 个解决方案

#1


75  

Ok, not a C/C++ expert by any means, but I thought the point of a header file was to declare the functions, then the C/CPP file was to define the implementation.

好吧,无论如何都不是一个C/ c++专家,但是我认为头文件的要点是声明函数,然后C/CPP文件定义实现。

The true purpose of a header file is to share code amongst multiple source files. It is commonly used to separate declarations from implementations for better code management, but that is not a requirement. It is possible to write code that does not rely on header files, and it is possible to write code that is made up of just header files (the STL and Boost libraries are good examples of that). Remember, when the preprocessor encounters an #include statement, it replaces the statement with the contents of the file being referenced, then the compiler only sees the completed pre-processed code.

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

So, for example, if you have the following files:

例如,如果你有以下文件:

Foo.h:

foo。:

#ifndef FooH
#define FooH

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

#endif

Foo.cpp:

Foo.cpp:

#include "Foo.h"

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cpp:

Bar.cpp:

#include "Foo.h"

Foo f;
UInt32 chans = f.GetNumberChannels();

The preprocessor parses Foo.cpp and Bar.cpp separately and produces the following code that the compiler then parses:

预处理器解析Foo。cpp和酒吧。分别生成以下编译器解析的代码:

Foo.cpp:

Foo.cpp:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cpp:

Bar.cpp:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

Foo f;
UInt32 chans = f.GetNumberChannels();

Bar.cpp compiles into Bar.obj and contains a reference to call into Foo::GetNumberChannels(). Foo.cpp compiles into Foo.obj and contains the actual implementation of Foo::GetNumberChannels(). After compiling, the linker then matches up the .obj files and links them together to produce the final executable.

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

So why is there an implementation in a header?

那么为什么头中有实现呢?

By including the method implementation inside the method declaration, it is being implicitly declared as inlined (there is an actual inline keyword that can be explicitly used as well). Indicating that the compiler should inline a function is only a hint which does not guarantee that the function will actually get inlined. But if it does, then wherever the inlined function is called from, the contents of the function are copied directly into the call site, instead of generating a CALL statement to jump into the function and jump back to the caller upon exiting. The compiler can then take the surrounding code into account and optimize the copied code further, if possible. 

通过在方法声明中包含方法实现,它被隐式地声明为内联(还有一个实际的内联关键字也可以显式地使用)。指示编译器应该内联一个函数,这只是一个提示,并不保证这个函数会真正内联。但是,如果它这样做了,那么无论从哪里调用内联函数,函数的内容都被直接复制到调用站点,而不是生成一个调用语句,以便在退出时跳转到函数并跳转回调用者。编译器可以考虑周围的代码,如果可能的话,进一步优化复制的代码。

Does it have to do with the const keyword?

这和const关键字有关吗?

No. The const keyword merely indicates to the compiler that the method will not alter the state of the object it is being called on at runtime.

不。const关键字只是向编译器表明,该方法不会改变在运行时调用的对象的状态。

What exactly is the benefit/point of doing it this way vs. defining the implementation in the CPP file?

这样做与在CPP文件中定义实现究竟有什么好处/意义?

When used effectively, it allows the compiler to usually produce faster and better optimized machine code.

当有效使用时,它允许编译器生成更快更好的优化机器代码。

#2


21  

It is perfectly valid to have an implementation of a function in a header file. The only issue with this is breaking the one-definition-rule. That is, if you include the header from multiple other files, you will get a compiler error.

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

However, there is one exception. If you declare a function to be inline, it is exempt from the one-definition-rule. This is what is happening here, since member functions defined inside a class definition are implicitly inline.

然而,有一个例外。如果您声明一个函数是内联的,它就不受一个定义规则的约束。这就是这里所发生的事情,因为在类定义中定义的成员函数是隐式内联的。

Inline itself is a hint to the compiler that a function may be a good candidate for inlining. That is, expanding any call to it into the definition of the function, rather than a simple function call. This is an optimization which trades the size of the generated file for faster code. In modern compilers, providing this inlining hint for a function is mostly ignored, except for the effects it has on the one-definition-rule. Also, a compiler is always free to inline any function it sees fit, even if it has not been declared inline (explicitly or implicitly).

内联本身是对编译器的一种提示,即函数可能是内联的好候选者。也就是说,将对它的任何调用扩展到函数的定义中,而不是简单的函数调用。这是一种优化,将生成的文件的大小转换为更快的代码。在现代编译器中,为函数提供内联提示通常被忽略,除了它对一个定义规则的影响。而且,编译器总是可以*地内联它认为合适的任何函数,即使它没有被声明为内联(显式或隐式)。

In your example, the use of const after the argument list signals that the member function does not modify the object on which it is called. In practice, this means that the object pointed to by this, and by extension all class members, will be considered const. That is, trying to modify them will generate a compile-time error.

在您的示例中,在参数列表之后使用const表示成员函数不修改被调用对象。在实践中,这意味着由它指向的对象以及扩展后的所有类成员将被视为const。也就是说,试图修改它们将产生编译时错误。

#3


4  

It is implicitly declared inline by virtue of being a member function defined within the class declaration. This does not mean the compiler has to inline it, but it means you won't break the one definition rule. It is completely unrelated to const*. It is also unrelated to the length and complexity of the function.

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

If it were a non-member function, then you would have to explicitly declare it as inline:

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

inline void foo() { std::cout << "foo!\n"; }

* See here for more on const at the end of a member function.

*查看更多关于成员函数末尾的const。

#4


2  

Even in plain C, it is possible to put code in a header file. If you do it, you usually need to declare it static or else multiple .c files including the same header will cause a "multiply defined function" error.

即使是在普通的C语言中,也可以将代码放在头文件中。如果这样做,通常需要声明它为静态的,或者多个.c文件(包括相同的头)将导致“多重定义函数”错误。

The preprocessor textually includes an include file, so the code in an include file becomes part of the source file (at least from the compiler's point of view).

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

The designers of C++ wanted to enable object-oriented programming with good data hiding, so they expected to see lots of getter and setter functions. They didn't want an unreasonable performance penalty. So, they designed C++ so that the getters and setters could not only be declared in the header but actually implemented, so they would inline. That function you showed is a getter, and when that C++ code is compiled, there won't be any function call; code to fetch out that value will just be compiled in place.

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

It is possible to make a computer language that doesn't have the header file/source file distinction, but just has actual "modules" that the compiler understands. (C++ didn't do that; they just built on top of the successful C model of source files and textually included header files.) If source files are modules, it would be possible for a compiler to pull code out of the module and then inline that code. But the way C++ did it is simpler to implement.

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

#5


1  

As far as I know, there are two kinds of methods, which can be safely implemented inside the header file.

据我所知,有两种方法可以在头文件中安全地实现。

  • Inline methods - their implementation is copied to places, where they are used, so there is no problem with double-definition linker errors;
  • 内联方法——它们的实现被复制到使用它们的地方,因此双定义链接器错误没有问题;
  • Template methods - they are actually compiled at the moment of template instantiation (eg. when someone inputs a type in place of template), so again there is no possibility of double-definition problem.
  • 模板方法——它们实际上是在模板实例化时被编译的。当有人输入一个类型代替模板时),那么再一次,就不可能出现双重定义问题。

I believe, your example fits the first case.

我相信你的例子符合第一种情况。

#6


0  

Keeping the implementation in the class header file works, as I'm sure you know if you compiled your code. The const keyword ensures you don't change any members, it keeps the instance immutable for the duration of the method call.

将实现保存在类头文件中可以工作,因为我确信您知道您是否编译了代码。const关键字确保您不更改任何成员,它使实例在方法调用期间保持不变。

#1


75  

Ok, not a C/C++ expert by any means, but I thought the point of a header file was to declare the functions, then the C/CPP file was to define the implementation.

好吧,无论如何都不是一个C/ c++专家,但是我认为头文件的要点是声明函数,然后C/CPP文件定义实现。

The true purpose of a header file is to share code amongst multiple source files. It is commonly used to separate declarations from implementations for better code management, but that is not a requirement. It is possible to write code that does not rely on header files, and it is possible to write code that is made up of just header files (the STL and Boost libraries are good examples of that). Remember, when the preprocessor encounters an #include statement, it replaces the statement with the contents of the file being referenced, then the compiler only sees the completed pre-processed code.

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

So, for example, if you have the following files:

例如,如果你有以下文件:

Foo.h:

foo。:

#ifndef FooH
#define FooH

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

#endif

Foo.cpp:

Foo.cpp:

#include "Foo.h"

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cpp:

Bar.cpp:

#include "Foo.h"

Foo f;
UInt32 chans = f.GetNumberChannels();

The preprocessor parses Foo.cpp and Bar.cpp separately and produces the following code that the compiler then parses:

预处理器解析Foo。cpp和酒吧。分别生成以下编译器解析的代码:

Foo.cpp:

Foo.cpp:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

UInt32 Foo::GetNumberChannels() const
{
    return _numberChannels;
}

Bar.cpp:

Bar.cpp:

class Foo
{
public:
    UInt32 GetNumberChannels() const;

private:
    UInt32 _numberChannels;
};

Foo f;
UInt32 chans = f.GetNumberChannels();

Bar.cpp compiles into Bar.obj and contains a reference to call into Foo::GetNumberChannels(). Foo.cpp compiles into Foo.obj and contains the actual implementation of Foo::GetNumberChannels(). After compiling, the linker then matches up the .obj files and links them together to produce the final executable.

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

So why is there an implementation in a header?

那么为什么头中有实现呢?

By including the method implementation inside the method declaration, it is being implicitly declared as inlined (there is an actual inline keyword that can be explicitly used as well). Indicating that the compiler should inline a function is only a hint which does not guarantee that the function will actually get inlined. But if it does, then wherever the inlined function is called from, the contents of the function are copied directly into the call site, instead of generating a CALL statement to jump into the function and jump back to the caller upon exiting. The compiler can then take the surrounding code into account and optimize the copied code further, if possible. 

通过在方法声明中包含方法实现,它被隐式地声明为内联(还有一个实际的内联关键字也可以显式地使用)。指示编译器应该内联一个函数,这只是一个提示,并不保证这个函数会真正内联。但是,如果它这样做了,那么无论从哪里调用内联函数,函数的内容都被直接复制到调用站点,而不是生成一个调用语句,以便在退出时跳转到函数并跳转回调用者。编译器可以考虑周围的代码,如果可能的话,进一步优化复制的代码。

Does it have to do with the const keyword?

这和const关键字有关吗?

No. The const keyword merely indicates to the compiler that the method will not alter the state of the object it is being called on at runtime.

不。const关键字只是向编译器表明,该方法不会改变在运行时调用的对象的状态。

What exactly is the benefit/point of doing it this way vs. defining the implementation in the CPP file?

这样做与在CPP文件中定义实现究竟有什么好处/意义?

When used effectively, it allows the compiler to usually produce faster and better optimized machine code.

当有效使用时,它允许编译器生成更快更好的优化机器代码。

#2


21  

It is perfectly valid to have an implementation of a function in a header file. The only issue with this is breaking the one-definition-rule. That is, if you include the header from multiple other files, you will get a compiler error.

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

However, there is one exception. If you declare a function to be inline, it is exempt from the one-definition-rule. This is what is happening here, since member functions defined inside a class definition are implicitly inline.

然而,有一个例外。如果您声明一个函数是内联的,它就不受一个定义规则的约束。这就是这里所发生的事情,因为在类定义中定义的成员函数是隐式内联的。

Inline itself is a hint to the compiler that a function may be a good candidate for inlining. That is, expanding any call to it into the definition of the function, rather than a simple function call. This is an optimization which trades the size of the generated file for faster code. In modern compilers, providing this inlining hint for a function is mostly ignored, except for the effects it has on the one-definition-rule. Also, a compiler is always free to inline any function it sees fit, even if it has not been declared inline (explicitly or implicitly).

内联本身是对编译器的一种提示,即函数可能是内联的好候选者。也就是说,将对它的任何调用扩展到函数的定义中,而不是简单的函数调用。这是一种优化,将生成的文件的大小转换为更快的代码。在现代编译器中,为函数提供内联提示通常被忽略,除了它对一个定义规则的影响。而且,编译器总是可以*地内联它认为合适的任何函数,即使它没有被声明为内联(显式或隐式)。

In your example, the use of const after the argument list signals that the member function does not modify the object on which it is called. In practice, this means that the object pointed to by this, and by extension all class members, will be considered const. That is, trying to modify them will generate a compile-time error.

在您的示例中,在参数列表之后使用const表示成员函数不修改被调用对象。在实践中,这意味着由它指向的对象以及扩展后的所有类成员将被视为const。也就是说,试图修改它们将产生编译时错误。

#3


4  

It is implicitly declared inline by virtue of being a member function defined within the class declaration. This does not mean the compiler has to inline it, but it means you won't break the one definition rule. It is completely unrelated to const*. It is also unrelated to the length and complexity of the function.

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

If it were a non-member function, then you would have to explicitly declare it as inline:

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

inline void foo() { std::cout << "foo!\n"; }

* See here for more on const at the end of a member function.

*查看更多关于成员函数末尾的const。

#4


2  

Even in plain C, it is possible to put code in a header file. If you do it, you usually need to declare it static or else multiple .c files including the same header will cause a "multiply defined function" error.

即使是在普通的C语言中,也可以将代码放在头文件中。如果这样做,通常需要声明它为静态的,或者多个.c文件(包括相同的头)将导致“多重定义函数”错误。

The preprocessor textually includes an include file, so the code in an include file becomes part of the source file (at least from the compiler's point of view).

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

The designers of C++ wanted to enable object-oriented programming with good data hiding, so they expected to see lots of getter and setter functions. They didn't want an unreasonable performance penalty. So, they designed C++ so that the getters and setters could not only be declared in the header but actually implemented, so they would inline. That function you showed is a getter, and when that C++ code is compiled, there won't be any function call; code to fetch out that value will just be compiled in place.

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

It is possible to make a computer language that doesn't have the header file/source file distinction, but just has actual "modules" that the compiler understands. (C++ didn't do that; they just built on top of the successful C model of source files and textually included header files.) If source files are modules, it would be possible for a compiler to pull code out of the module and then inline that code. But the way C++ did it is simpler to implement.

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

#5


1  

As far as I know, there are two kinds of methods, which can be safely implemented inside the header file.

据我所知,有两种方法可以在头文件中安全地实现。

  • Inline methods - their implementation is copied to places, where they are used, so there is no problem with double-definition linker errors;
  • 内联方法——它们的实现被复制到使用它们的地方,因此双定义链接器错误没有问题;
  • Template methods - they are actually compiled at the moment of template instantiation (eg. when someone inputs a type in place of template), so again there is no possibility of double-definition problem.
  • 模板方法——它们实际上是在模板实例化时被编译的。当有人输入一个类型代替模板时),那么再一次,就不可能出现双重定义问题。

I believe, your example fits the first case.

我相信你的例子符合第一种情况。

#6


0  

Keeping the implementation in the class header file works, as I'm sure you know if you compiled your code. The const keyword ensures you don't change any members, it keeps the instance immutable for the duration of the method call.

将实现保存在类头文件中可以工作,因为我确信您知道您是否编译了代码。const关键字确保您不更改任何成员,它使实例在方法调用期间保持不变。