将 C++ 类的定义放入头文件是一个好习惯吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4955159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:00:56  来源:igfitidea点击:

Is is a good practice to put the definition of C++ classes into the header file?

c++declarationdefinitionheader-files

提问by sepisoad

When we design classes in Java, Vala, or C# we put the definition and declaration in the same source file. But in C++ it is traditionally preferred to separate the definition and declaration in two or more files.

当我们用 Java、Vala 或 C# 设计类时,我们将定义和声明放在同一个源文件中。但是在 C++ 中,传统上更喜欢将定义和声明分开在两个或多个文件中。

What happens if I just use a header file and put everything into it, like Java? Is there a performance penalty or something?

如果我只使用头文件并将所有内容都放入其中,例如 Java,会发生什么?是否有性能惩罚之类的?

回答by templatetypedef

The answer depends on what kind of class you're creating.

答案取决于您创建的课程类型。

C++'s compilation model dates back to the days of C, and so its method of importing data from one source file into another is comparatively primitive. The #includedirective literally copies the contents of the file you're including into the source file, then treats the result as though it was the file you had written all along. You need to be careful about this because of a C++ policy called the one definition rule(ODR) which states, unsurprisingly, that every function and class should have at most one definition. This means that if you declare a class somewhere, all of that class's member functions should be either not defined at all or defined exactly once in exactly one file. There are some exceptions (I'll get to them in a minute), but for now just treat this rule as if it's a hard-and-fast, no-exceptions rule.

C++ 的编译模型可以追溯到 C 时代,因此其将数据从一个源文件导入另一个源文件的方法比较原始。该#include指令逐字地将您包含的文件的内容复制到源文件中,然后将结果视为您一直编写的文件。您需要小心这一点,因为 C++ 策略称为一个定义规则(ODR) 毫不奇怪地指出,每个函数和类最多应该有一个定义。这意味着如果您在某处声明一个类,则该类的所有成员函数要么根本不定义,要么只在一个文件中定义一次。有一些例外(我将在稍后介绍它们),但现在只需将此规则视为一条硬性规定,无例外规则。

If you take a non-template class and put both the class definition and the implementation into a header file, you might run into trouble with the one definition rule. In particular, suppose that I have two different .cpp files that I compile, both of which #includeyour header containing both the implementation and the interface. In this case, if I try linking those two files together, the linker will find that each one contains a copy of the implementation code for the class's member functions. At this point, the linker will report an error because you have violated the one definition rule: there are two different implementations of all the class's member functions.

如果您采用非模板类并将类定义和实现都放入头文件中,您可能会遇到单一定义规则的麻烦。特别是,假设我编译了两个不同的 .cpp 文件,这两个文件都#include包含实现和接口。在这种情况下,如果我尝试将这两个文件链接在一起,链接器会发现每个文件都包含类成员函数的实现代码的副本。此时,链接器会报错,因为你违反了一个定义规则:类的所有成员函数都有两种不同的实现。

To prevent this, C++ programmers typically split classes up into a header file which contains the class declaration, along with the declarations of its member functions, without the implementations of those functions. The implementations are then put into a separate .cpp file which can be compiled and linked separately. This allows your code to avoid running into trouble with the ODR. Here's how. First, whenever you #includethe class header file into multiple different .cpp files, each of them just gets a copy of the declarationsof the member functions, not their definitions, and so none of your class's clients will end up with the definitions. This means that any number of clients can #includeyour header file without running into trouble at link-time. Since your own .cpp file with the implementation is the sole file that contains the implementations of the member functions, at link time you can merge it with any number of other client object files without a hassle. This is the main reason that you split the .h and .cpp files apart.

为了防止这种情况,C++ 程序员通常将类拆分为一个头文件,其中包含类声明及其成员函数的声明,而没有这些函数的实现。然后将实现放入一个单独的 .cpp 文件中,该文件可以单独编译和链接。这允许您的代码避免遇到 ODR 问题。就是这样。首先,每当您#include将类头文件转换为多个不同的 .cpp 文件时,每个文件都只会获得成员函数声明的副本,而不是它们的定义,因此您的类的客户端都不会得到定义。这意味着任意数量的客户端都可以#include您的头文件而不会在链接时遇到麻烦。由于您自己的带有实现的 .cpp 文件是包含成员函数实现的唯一文件,因此在链接时您可以轻松地将其与任意数量的其他客户端对象文件合并。这是将 .h 和 .cpp 文件分开的主要原因。

Of course, the ODR has a few exceptions. The first of these comes up with template functions and classes. The ODR explicitly states that you can have multiple different definitions for the same template class or function, provided that they're all equivalent. This is primarily to make it easier to compile templates - each C++ file can instantiate the same template without colliding with any other files. For this reason, and a few other technical reasons, class templates tend to just have a .h file without a matching .cpp file. Any number of clients can #includethe file without trouble.

当然,ODR 也有一些例外。第一个是模板函数和类。ODR 明确指出您可以为同一个模板类或函数有多个不同的定义,前提是它们都是等价的。这主要是为了更容易编译模板——每个 C++ 文件都可以实例化同一个模板,而不会与任何其他文件发生冲突。出于这个原因以及其他一些技术原因,类模板往往只有一个 .h 文件,而没有匹配的 .cpp 文件。任何数量的客户端都可以#include毫无问题地处理文件。

The other major exception to the ODR involves inline functions. The spec specifically states that the ODR does not apply to inline functions, so if you have a header file with an implementation of a class member function that's marked inline, that's perfectly fine. Any number of files can #includethis file without breaking the ODR. Interestingly, any member function that's declared and defined in the body of a class is implicitly inline, so if you have a header like this:

ODR 的另一个主要例外涉及内联函数。该规范特别指出 ODR 不适用于内联函数,因此如果您有一个包含标记为内联的类成员函数的实现的头文件,那完全没问题。任意数量的文件都可以#include在不破坏 ODR 的情况下创建该文件。有趣的是,在类的主体中声明和定义的任何成员函数都是隐式内联的,因此如果您有这样的标头:

#ifndef Include_Guard
#define Include_Guard

class MyClass {
public:
    void DoSomething() {
        /* ... code goes here ... */
    }
};

#endif

Then you're not risking breaking the ODR. If you rewrite this as

那么您就不必冒险打破 ODR。如果您将其重写为

#ifndef Include_Guard
#define Include_Guard

class MyClass {
public:
    void DoSomething();
};

void MyClass::DoSomething()  {
    /* ... code goes here ... */
}

#endif

then you wouldbe breaking the ODR, since the member function isn't marked inline and if multiple clients #includethis file there will be multiple definitions of MyClass::DoSomething.

那么你破坏 ODR,因为成员函数没有被标记为内联,如果有多个客户端,#include这个文件将有多个MyClass::DoSomething.

So to summarize - you should probably split up your classes into a .h/.cpp pair to avoid breaking the ODR. However, if you're writing a class template, you don't need the .cpp file (and probably shouldn't have one at all), and if you're okay marking every single member function of your class inline you can also avoid the .cpp file.

总而言之 - 您应该将您的类分成一个 .h/.cpp 对,以避免破坏 ODR。但是,如果您正在编写类模板,则不需要 .cpp 文件(并且可能根本不应该有一个),并且如果您可以内联标记类的每个成员函数,您也可以避免 .cpp 文件。

回答by sachin

The drawback of putting definition in header files is as follows:-

将定义放在头文件中的缺点如下:-

Header file A - contains definition of metahodA()

头文件 A - 包含 metahodA() 的定义

Header file B - includes header file A.

头文件 B - 包括头文件 A。

Now let us say you change the definition of methodA. You would need to compile file A as well as B because of the inclusion of header file A in B.

现在让我们假设您更改了 methodA 的定义。您需要编译文件 A 和 B,因为在 B 中包含头文件 A。

回答by regality

The biggest difference is that every function is declared as an inline function. Generally your compiler will be smart enough that this won't be a problem, but worst case scenario it will cause page faults on a regular basis and make your code embarrassingly slow. Usually the code is separated for design reasons, and not for performance.

最大的区别是每个函数都被声明为内联函数。通常,您的编译器足够智能,这不会成为问题,但在最坏的情况下,它会定期导致页面错误并使您的代码慢得令人尴尬。通常出于设计原因而不是出于性能原因将代码分开。

回答by Mahesh

In general, it is a good practice to seperate implementation from headers. However, there are exceptions in cases like templates where the implementation goes in the header itself.

通常,将实现与标头分开是一个很好的做法。但是,在模板等情况下存在例外,其中实现位于标头本身中。

回答by ymett

Two particular problems with putting everything in the header:

将所有内容放在标题中的两个特殊问题:

  1. Compile times will be increased, sometimes greatly. C++ compile times are long enough that that's not something you want.

  2. If you have circular dependencies in the implementation, keeping everything in headers is difficult to impossible. eg:

    header1.h

    struct C1
    {
      void f();
      void g();
    };
    

    header2.h

    struct C2
    {
      void f();
      void g();
    };
    

    impl1.cpp

    #include "header1.h"
    #include "header2.h"
    
    void C1::f()
    {
      C2 c2;
      c2.f();
    }
    

    impl2.cpp

    #include "header2.h"
    #include "header1.h"
    
    void C2::g()
    {
      C1 c1;
      c1.g();
    }
    
  1. 编译时间会增加,有时会增加很多。C++ 编译时间足够长,这不是您想要的。

  2. 如果您在实现中有循环依赖,那么将所有内容都保存在标题中是很难甚至不可能的。例如:

    头文件1.h

    struct C1
    {
      void f();
      void g();
    };
    

    头文件2.h

    struct C2
    {
      void f();
      void g();
    };
    

    impl1.cpp

    #include "header1.h"
    #include "header2.h"
    
    void C1::f()
    {
      C2 c2;
      c2.f();
    }
    

    impl2.cpp

    #include "header2.h"
    #include "header1.h"
    
    void C2::g()
    {
      C1 c1;
      c1.g();
    }