首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >将C++类代码分成多个文件,规则是什么?

将C++类代码分成多个文件,规则是什么?
EN

Stack Overflow用户
提问于 2013-08-30 22:41:10
回答 3查看 37.6K关注 0票数 30

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

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

节省空间的Reason 1

您有一个文件,其中包含具有所有类成员的类的声明。如果你在两个不同的头文件中包含该文件,然后将其包含在源文件中,那么你可以在该文件周围放置#include保护(或一次#杂注),以确保不会出现冲突。您可以使用该类中声明的任何方法的实现编译一个单独的源文件,因为它从源文件中卸载了许多行代码,这会稍微清理一些东西,并为您的程序引入一些顺序。

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

代码语言:javascript
复制
// 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 2

也许这就是为什么你要将实现从声明中分离出来的主要原因。在上面的示例中,您可以将方法体移动到类之外。这会让它看起来更整洁和有条理。但是,根据此question,上面的示例具有隐式inline说明符。如下例所示,将实现从类内移到类外将导致链接器错误,因此您要么内联所有内容,要么将函数定义移动到.cpp文件。

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

代码语言:javascript
复制
// 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.
}

要解决此问题,请执行以下操作:

代码语言:javascript
复制
//my_class.cpp
void my_class::my_function()
{
    // Now in a .cpp file, so no multiple definition error
}

或者:

代码语言:javascript
复制
// 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 3您再次希望节省空间,但这一次您使用的是模板类:

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

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

代码语言:javascript
复制
// 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。但是,您不能将该文件包含在其他任何位置,因此不能包含.hpp,而应包含.tpp

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

提问时间

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

归根结底,这就是如何在文件中组织代码。我想我已经想出了一个永远有效的结构。

这是我想出来的。(如果您愿意,这是我的类代码文件组织/结构标准。(我不知道它是否会非常有用,这就是询问的意义。)

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

这样的标准在工业中存在吗?我提出的标准会在所有情况下都有效吗?

EN

回答 3

Stack Overflow用户

发布于 2013-08-30 22:58:00

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

以下是我如何安排我的文件。对于简单的类,我省略了转发声明文件。

myclass-fwd.h

代码语言:javascript
复制
#pragma once

namespace NS
{
class MyClass;
}

myclass.h

代码语言:javascript
复制
#pragma once
#include "headers-needed-by-header"
#include "myclass-fwd.h"

namespace NS
{
class MyClass
{
    ..
};
}

myclass.cpp

代码语言:javascript
复制
#include "headers-needed-by-source"
#include "myclass.h"

namespace
    {
    void LocalFunc();
}

NS::MyClass::...

根据首选项将杂注替换为标题保护。

这种方法的原因是减少头依赖,这会减慢大型项目中的编译时间。如果你不知道,你可以向前声明一个类作为指针或引用。仅当构造、创建或使用类的成员时,才需要完整声明。

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

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

票数 6
EN

Stack Overflow用户

发布于 2013-08-31 00:03:35

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

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

票数 3
EN

Stack Overflow用户

发布于 2013-08-31 00:25:08

这样的标准在行业中存在吗?

是。话又说回来,与您所表达的标准大不相同的编码标准也可以在行业中找到。毕竟,您谈论的是编码标准,而编码标准的范围从好到差再到丑陋。

我提出的标准在所有情况下都能工作吗?

绝对不是。例如,

代码语言:javascript
复制
template <typename T> class Foo {
public:
   void some_method (T& arg);
...
};

在这里,类模板Foo的定义对模板参数T一无所知。如果对于某些类模板,方法的定义根据模板参数的不同而不同呢?你的第二条规则在这里是行不通的。

另一个例子:如果相应的源文件很大,有上千行或更长怎么办?有时,在多个源文件中提供实现是有意义的。有些标准走到了每个文件一个函数的极端(个人观点:耶!)。

在上千行长的源文件的另一个极端是没有源文件的类。整个实现都在头中。对于只使用头的实现来说,有很多东西要说。即使没有其他问题,它也简化了链接问题,有时甚至非常明显。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18535621

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档