30

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

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

原因1 为了节省空间:

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

示例:如您所见,可以通过将类方法的实现拆分到不同的文件来改进以下示例。(一个 .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
}

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

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

示例:_如果您不将函数定义移动到 .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.
}

要解决问题:

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

或者:

// 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
}

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

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

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

// 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
}

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

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

提问时间

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

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

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

  • 1:在文件中声明类(模板或其他).hpp,包括所有方法、友元函数和数据。
  • 2:.hpp文件的底部,包含任何方法的实现#include的文件。创建文件并确保所有方法都指定为..tppinline.tppinline
  • 3:所有其他成员(非内联函数、友元函数和静态数据)都应该定义在一个.cpp文件中,该#include文件.hpp位于顶部的文件中,以防止出现“未声明类ABC”之类的错误。由于此文件中的所有内容都将具有外部链接,因此程序将正确链接。

行业中是否存在这样的标准?我提出的标准是否适用于所有情况?

4

3 回答 3

6

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

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

myclass-fwd.h

#pragma once

namespace NS
{
class MyClass;
}

我的班级.h

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

namespace NS
{
class MyClass
{
    ..
};
}

我的类.cpp

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

namespace
    {
    void LocalFunc();
}

NS::MyClass::...

根据偏好将编译指示替换为标头保护..

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

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

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

于 2013-08-30T14:58:00.133 回答
3

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

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

于 2013-08-30T16:03:34.920 回答
0

行业中是否存在这样的标准?

是的。再说一次,与您所表达的编码标准大不相同的编码标准也可以在工业中找到。毕竟,您在谈论编码标准,编码标准的范围从好到坏到丑陋。

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

绝对不。例如,

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

在这里,类模板的定义对Foo模板参数T一无所知。如果对于某些类模板,方法的定义取决于模板参数怎么办?您的规则 #2 在这里不起作用。

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

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

于 2013-08-30T16:25:08.400 回答