C++ 将C++类代码分割成多个文件,有哪些规则?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18535621/
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-27 22:00:17  来源:igfitidea点击:

Separating C++ Class Code into Multiple Files, what are the rules?

c++classinclude

提问by user3728501

Thinking Time - Why do you want to split your file anyway?

思考时间 - 你为什么要拆分你的文件?

As the title suggests, the end problem I have is multiple definition linker errors. I have actually fixed the problem, but I haven't fixed the problem in the correct way. Before starting I want to discuss the reasons for splitting a class file into multiple files. I have tried to put all the possible scenarios here - if I missed any, please remind me and I can make changes. Hopefully the following are correct:

正如标题所暗示的,我遇到的最终问题是多个定义链接器错误。我实际上已经解决了问题,但我没有以正确的方式解决问题。在开始之前,我想讨论将类文件拆分为多个文件的原因。我已经尝试将所有可能的情况放在这里 - 如果我错过了任何一个,请提醒我,我可以进行更改。希望以下内容是正确的:

Reason 1To save space:

原因1节省空间:

You have a file containing the declaration of a class with all class members. You place #include guards around this file (or #pragma once) to ensure no conflicts arise if you #include the file in two different header files which are then included in a source file. You compile a separate source file with the implementation of any methods declared in this class, as it offloads many lines of code from your source file, which cleans things up a bit and introduces some order to your program.

您有一个包含所有类成员的类声明的文件。您在此文件周围放置 #include 保护(或 #pragma once)以确保在将文件 #include 包含在两个不同的头文件中时不会发生冲突,然后将它们包含在源文件中。您使用此类中声明的任何方法的实现编译一个单独的源文件,因为它从源文件中卸载了许多代码行,这会稍微清理一下并为您的程序引入一些顺序。

Example: As you can see, the below example could be improved by splitting the implementation of the class methods into a different file. (A .cpp file)

示例:如您所见,可以通过将类方法的实现拆分到不同的文件中来改进以下示例。(一个 .cpp 文件)

// my_class.hpp
#pragma once

class my_class
{
public:
    void my_function()
    {
        // LOTS OF CODE
        // CONFUSING TO DEBUG
        // LOTS OF CODE
        // DISORGANIZED AND DISTRACTING
        // LOTS OF CODE
        // LOOKS HORRIBLE
        // LOTS OF CODE
        // VERY MESSY
        // LOTS OF CODE
    }

    // MANY OTHER METHODS
    // MEANS VERY LARGE FILE WITH LOTS OF LINES OF CODE
}

Reason 2To prevent multiple definition linker errors:

原因 2防止多个定义链接器错误:

Perhaps this is the main reason why you would split implementation from declaration. In the above example, you could move the method body to outside the class. This would make it look much cleaner and structured. However, according to this question, the above example has implicit inlinespecifiers. Moving the implementation from within the class to outside the class, as in the example below, will cause you linker errors, and so you would either inline everything, or move the function definitions to a .cpp file.

也许这就是您将实现与声明分开的主要原因。在上面的示例中,您可以将方法主体移到类之外。这将使它看起来更干净和结构化。然而,根据这个问题,上面的例子有隐含的inline说明符。将实现从类内移动到类外,如下例所示,将导致链接器错误,因此您要么内联所有内容,要么将函数定义移动到 .cpp 文件。

Example: _The example below will cause "multiple definition linker errors" if you do not move the function definition to a .cpp file or specify the function as inline.

示例: _如果您不将函数定义移动到 .cpp 文件或将函数指定为内联函数,则下面的示例将导致“多定义链接器错误”。

// my_class.hpp
void my_class::my_function()
{
    // ERROR! MULTIPLE DEFINITION OF my_class::my_function
    // This error only occurs if you #include the file containing this code
    // in two or more separate source (compiled, .cpp) files.
}

To fix the problem:

要解决问题:

//my_class.cpp
void my_class::my_function()
{
    // Now in a .cpp file, so no multiple definition error
}

Or:

或者:

// my_class.hpp
inline void my_class::my_function()
{
    // Specified function as inline, so okay - note: back in header file!
    // The very first example has an implicit `inline` specifier
}

Reason 3You want to save space, again, but this time you are working with a template class:

原因 3您想再次节省空间,但这次您使用的是模板类:

If we are working with template classes, then we cannot move the implementation to a source file (.cpp file). That's not currently allowed by (I assume) either the standard or by current compilers. Unlike the first example of Reason 2, above, we are allowed to place the implementation in the header file. According to this questionthe reason is that template class methods also have implied inlinespecifiers. Is that correct? (It seems to make sense.) But nobody seemed to know on the question I have just referenced!

如果我们使用模板类,则无法将实现移动到源文件(.cpp 文件)。目前(我假设)标准或当前编译器都不允许这样做。与上面原因 2的第一个示例不同,我们可以将实现放在头文件中。根据这个问题,原因是模板类方法也有隐含的inline说明符。那是对的吗?(这似乎有道理。)但似乎没有人知道我刚刚提到的问题!

So, are the two examples below identical?

那么,下面的两个例子是否相同?

// some_header_file.hpp
#pragma once

// template class declaration goes here
class some_class
{
    // Some code
};

// Example 1: NO INLINE SPECIFIER
template<typename T>
void some_class::class_method()
{
    // Some code
}

// Example 2: INLINE specifier used
template<typename T>
inline void some_class::class_method()
{
    // Some code
}

If you have a template class header file, which is becoming huge due to all the functions you have, then I believe you are allowed to move the function definitions to another header file (usually a .tpp file?) and then #include file.tppat the end of your header file containing the class declaration. You must NOT include this file anywhere else, however, hence the .tpprather than .hpp.

如果您有一个模板类头文件,由于您拥有的所有功能而变得庞大,那么我相信您可以将函数定义移动到另一个头文件(通常是 .tpp 文件?),然后#include file.tpp在末尾包含类声明的头文件。但是,您不能在其他任何地方包含此文件,因此,.tpp而不是.hpp.

I assume you could also do this with the inline methods of a regular class? Is that allowed also?

我假设您也可以使用常规类的内联方法来做到这一点?这也允许吗?

Question Time

提问时间

So I have made some statements above, most of which relate to the structuring of source files. I think everything I said was correct, because I did some basic research and "found out some stuff", but this is a question and so I don't know for sure.

所以我在上面做了一些陈述,其中大部分与源文件的结构有关。我认为我说的一切都是正确的,因为我做了一些基础研究并“发现了一些东西”,但这是一个问题,所以我不确定。

What this boils down to, is how you would organize code within files. I think I have figured out a structure which will always work.

这归结为,您将如何在文件中组织代码。我想我已经找到了一个永远有效的结构。

Here is what I have come up with. (This is my class code file organization/structure standard, if you like. Don't know if it will be very useful yet, that's the point of asking.)

这是我想出的。(这是我的类代码文件组织/结构标准,如果你喜欢。不知道它是否会很有用,这是问的重点。)

  • 1:Declare the class (template or otherwise) in a .hppfile, including all methods, friend functions and data.
  • 2:At the bottom of the .hppfile, #includea .tppfile containing the implementation of any inlinemethods. Create the .tppfile and ensure all methods are specified to be inline.
  • 3:All other members (non-inline functions, friend functions and static data) should be defined in a .cppfile, which #includes the .hppfile at the top to prevent errors like "class ABC has not been declared". Since everything in this file will have external linkage, the program will link correctly.
  • 1:.hpp文件中声明类(模板或其他),包括所有方法、友元函数和数据。
  • 2:.hpp文件的底部,#include一个.tpp包含任何inline方法实现的文件。创建.tpp文件并确保所有方法都指定为inline.
  • 3:所有其他成员(非内联函数,友元函数和静态数据)应该在定义.cpp文件,其中#includeS上的.hpp文件的顶部,以防止错误,如“ABC类尚未声明”。由于此文件中的所有内容都具有外部链接,因此程序将正确链接。

Do standards like this exist in industry? Will the standard I came up with work in all cases?

工业上有这样的标准吗?我提出的标准是否适用于所有情况?

回答by Neil Kirk

Your three points sound about right. That's the standard way to do things (although I've not seen .tpp extension before, usually it's .inl), although personally I just put inline functions at the bottom of header files rather than in a separate file.

你的三点听起来是对的。这是做事的标准方式(虽然我以前没有见过 .tpp 扩展名,通常是 .inl),尽管我个人只是将内联函数放在头文件的底部而不是单独的文件中。

Here is how I arrange my files. I omit the forward declare file for simple classes.

这是我整理文件的方式。我省略了简单类的前向声明文件。

myclass-fwd.h

myclass-fwd.h

#pragma once

namespace NS
{
class MyClass;
}

myclass.h

我的类.h

#pragma once
#include "headers-needed-by-header"
#include "myclass-fwd.h"

namespace NS
{
class MyClass
{
    ..
};
}

myclass.cpp

我的类.cpp

#include "headers-needed-by-source"
#include "myclass.h"

namespace
    {
    void LocalFunc();
}

NS::MyClass::...

Replace pragma with header guards according to preference..

根据偏好用头部防护替换 pragma ..

The reason for this approach is to reduce header dependencies, which slow down compile times in large projects. If you didn't know, you can forward declare a class to use as a pointer or reference. The full declaration is only needed when you construct, create or use members of the class.

这种方法的原因是减少头文件依赖性,这会减慢大型项目的编译时间。如果您不知道,您可以转发声明一个类以用作指针或引用。只有在构造、创建或使用类成员时才需要完整声明。

This means another class which uses the class (takes parameters by pointer/reference) only has to include the fwd header in its own header. The full header is then included in the second class's source file. This greatly reduces the amount of unneeded rubbish you get when pulling in a big header, which pulls in another big header, which pulls in another...

这意味着使用该类的另一个类(通过指针/引用获取参数)只需要在它自己的头中包含 fwd 头。然后将完整的头文件包含在第二个类的源文件中。这大大减少了在拉入一个大头时你得到的不需要的垃圾量,这会拉入另一个大头,然后拉入另一个......

The next tip is the unnamed namespace (sometimes called anonymous namespace). This can only appear in a source file and it is like a hidden namespace only visible to that file. You can place local functions, classes etc here which are only used by the the source file. This prevents name clashes if you create something with the same name in two different files. (Two local function F for example, may give linker errors).

下一个技巧是未命名的命名空间(有时称为匿名命名空间)。这只能出现在源文件中,就像一个隐藏的命名空间,只对那个文件可见。您可以在此处放置仅由源文件使用的本地函数、类等。如果您在两个不同的文件中创建具有相同名称的内容,这可以防止名称冲突。(例如两个本地函数 F,可能会出现链接器错误)。

回答by Pete Becker

The main reason to separate interface from implementation is so that you don't have to recompile all of your code when something in the implementation changes; you only have to recompile the source files that changed.

将接口与实现分开的主要原因是,当实现中的某些内容发生变化时,您不必重新编译所有代码;你只需要重新编译改变的源文件。

As for "Declare the class (template or otherwise)", a templateis nota class. A templateis a pattern for creatingclasses. More important, though, you definea class or a template in a header. The class definition includes declarations of its member functions, and non-inine member functions are defined in one or more source files. Inline member functions and all template functions should be defined in the header, by whatever combination of direct definitions and #includedirectives you prefer.

至于“声明类(模板或以其他方式)”,一个template不是一个class。Atemplate是用于创建类的模式。不过,更重要的是,您在标题中定义了一个类或一个模板。类定义包括其成员函数的声明,非inine 成员函数在一个或多个源文件中定义。内联成员函数和所有模板函数都应该在标头中定义,通过#include您喜欢的直接定义和指令的任何组合。

回答by David Hammen

Do standards like this exist in industry?

工业上有这样的标准吗?

Yes. Then again, coding standards that are rather different from the ones you expressed can also be found in industry. You are talking about coding standards, after all, and coding standards range from good to bad to ugly.

是的。再说一次,在行业中也可以找到与您表达的编码标准截然不同的编码标准。毕竟,您在谈论编码标准,编码标准从好到坏到丑陋。

Will the standard I came up with work in all cases?

我提出的标准是否适用于所有情况?

Absolutely not. For example,

绝对不。例如,

template <typename T> class Foo {
public:
   void some_method (T& arg);
...
};

Here, the definition of class template Foodoesn't know a thing about that template parameter T. What if, for some class template, the definitions of the methods vary depending on the template parameters? Your rule #2 just doesn't work here.

在这里,类模板的定义Foo不知道模板参数 T 的任何事情。如果对于某些类模板,方法的定义因模板参数而异怎么办?你的规则#2 在这里不起作用。

Another example: What if the corresponding source file is huge, a thousand lines long or longer? At times it makes sense to provide the implementation in multiple source files. Some standards go to the extreme of dictating one function per file (personal opinion: Yech!).

另一个例子:如果对应的源文件很大,一千行或更长怎么办?有时在多个源文件中提供实现是有意义的。一些标准走极端,规定每个文件一个功能(个人意见:是的!)。

At the other extreme of a thousand-plus line long source file is a class that has no source files. The entire implementation is in the header. There's a lot to be said for header-only implementations. If nothing else, it simplifies, sometimes significantly, the linking problem.

一千多行源文件的另一个极端是一个没有源文件的类。整个实现在标题中。对于仅标头的实现,有很多话要说。如果不出意外,它可以简化(有时是显着地)链接问题。