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

2024-03-17

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

如果我只使用一个头文件并将所有内容都放入其中,就像 Java 一样,会发生什么? 是否有性能损失或其他什么?


答案取决于您要创建什么类型的类。

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

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

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

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

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

#ifndef Include_Guard
#define Include_Guard

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

#endif

这样您就不必冒违反 ODR 的风险。如果你将其重写为

#ifndef Include_Guard
#define Include_Guard

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

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

#endif

然后你would会破坏 ODR,因为成员函数未标记为内联,并且如果有多个客户端#include这个文件会有多个定义MyClass::DoSomething.

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • Firebase 身份验证 - 电话 -“INVALID_CERT_HASH”

    当我尝试在本地 Android 设备中运行电话身份验证时 出现以下错误 GetAuthDomainTask Error getting project config Failed with E FirebaseAuth 25345 erro
  • 更改 NetBeans 项目中的 JRE

    我有一个使用 JRE 1 4 环境的 NetBeans 项目 这意味着我无法使用泛型 如何更改项目以使用 1 6 以便我可以使用泛型 In the Project选项卡 右键单击该项目并选择特性 在里面Library类别选择Java 平台
  • 在 C# 中更快地调用 PowerShell cmdlet

    我可以使用 C 中的 Powershell cmdlet 从 O365 获取用户详细信息 问题是获取时间 那太慢了 每个用户需要 2 秒 所以如果我有大量用户 就会导致时间问题 在这里我只想打印所有用户的信息 如名称 组详细信息 许可证 我
  • 在类内部使用具有非静态大小的数组

    我想使用一个可以在 4d 数组内为我存储值的类 Matrix 我想用SetSize为了设置的大小Matrix从我的main功能 class Value public int a int b int c int d sets the valu
  • Android 上的 Socket IO 连接失败

    我正在尝试使用 Socket IO Android 和 Node 创建一个简单的聊天 当我运行应用程序并尝试连接到服务器时 它总是因超时错误而失败 我不明白为什么 这是节点代码 app require express http requir
  • 使用 Watin 登录网页

    我尝试在网页上登录 网页上有两个带有输入的表单 输入具有相同的 Id 用户名 我怎样才能获得正确的输入来设置我的文本 这是我的错误代码 browser TextField Find ByName 用户名 TypeText test123 o
  • 如何从 8 位字节转换为 7 位字节(Base 256 到 Base 128)

    如何从 8 位字节转换为 7 位字节 Base 256 到 Base 128 我想做这样的事情 public string BytesToString byte in public byte StringToBytes string in
  • Angular Ag-Grid 无法正确显示

    我正在尝试使用角度Ag Grid https www ag grid com 在我的网络应用程序中 我已经遵循了这些教程 角网格 开始使用 ag Grid https www ag grid com angular getting star
  • Powershell 默认下拉值

    我有一个脚本 用户可以从下拉列表中选择选项 但如果用户没有选择任何内容 我就会收到错误 即使用户未输入值 如何设置返回的默认值 这是脚本 Edit This item to change the DropDown Values array
  • xquery 中的 SUM 和 GROUP BY 以及 1 个 xml 文件

    我有一个 SQL 查询 SELECT ShipVia SUM Freight FROM Orders GROUP BY ShipVia 它从访问数据库返回以下值 Ship Via TotalFreight 1 16 185 33 2 28
  • android - 如何创建可重用的函数?

    在我的 Android 项目中 我有很多活动 其中一些活动已经扩展了其他内容 例如地图活动或 BroadcastReceiver 如何创建一个可以从任何活动调用的函数 因为我不想在多个活动中重复任何代码 thanks 如果我有一些有用的函数
  • Scala 集合已排序、sortWith 和 sortBy 性能

    Scala在标准库中包含了几种用于对列表进行排序的方法 例如对列表进行排序list 可以使用 list sorted list sortWith lt list sortBy x gt x 虽然这些可能是对列表进行排序的最简单方法 但我发现
  • 用于分组 UITableView 的半透明 UITableViewCell?

    我想创建一个半透明分组表视图单元格 换句话说 我想看到分组的表格视图背景图案 但我不想要完全清晰的单元格 我见过很多关于透明单元的问题 但没有一个解决制作半透明 仅部分透明 单元的问题 这就是我正在尝试的 void tableView UI
  • 如何检测内存不足的段错误?

    如何检测段错误是否是由内存不足情况引起的 我有一个段错误 无法通过 valgrind 和 duma efence 进行诊断 因为它似乎使这些工具本身崩溃 Valgrind 不可能的事情发生了 duma mprotect 失败 无法分配内存
  • .sql 文件的存储过程

    SQL 2005 中是否有一个简单的过程可以将我的所有存储过程吐出到单独的 sql 文件中 我想将它们转移到 VSS 中 但我对单击每个文件获取源代码 将其转储到文本文件等的前景感到不太兴奋 在 SQL Management Studio
  • DocuSign 嵌入签名 returnUrl 长度限制?

    在处理 DocuSign 嵌入式签名流程 过去曾有效 时 我注意到在签署文档后 我被发送回的 returnUrl 中缺少 event 参数 returnUrl 看起来像 http www example com index php para
  • 什么是 Android 的 Smali 代码

    我将学习一些有关 Dalvik VM dex 和 Smali 的知识 我已经阅读过有关 smali 的内容 但仍然无法清楚地了解它在编译器链中的位置 以及它的目的是什么 这里有一些问题 据我所知 dalvik 与其他虚拟机一样运行字节码 对
  • Neo4J - 存储到关系与节点中

    我想知道将数据存储到关系或节点中是否有任何优点或缺点 例如 如果我要将与讨论相关的评论存储到数据库中 我应该将评论数据存储在 评论 关系中 还是通过单独的关系存储在与讨论相关的 评论 节点中 正确的数据模型取决于您需要进行的查询类型 您应该
  • 设计时和运行时的 WPF 数据上下文

    我正在学习 WPF MVVM Light 和 ViewModelLocator 模式 但在主窗口的数据上下文方面遇到了困难 public class ViewModelLocator public ViewModelLocator var
  • 将 C++ 类的定义放入头文件中是一个好习惯吗?

    当我们用 Java Vala 或 C 设计类时 我们将定义和声明放在同一个源文件中 但在 C 中 传统上首选将定义和声明分开在两个或多个文件中 如果我只使用一个头文件并将所有内容都放入其中 就像 Java 一样 会发生什么 是否有性能损失或