答案取决于你要创建什么样的类。
C++ 的编译模型可以追溯到 C 时代,因此其将数据从一个源文件导入另一个源文件的方法比较原始。 #include 指令实际上将您包含的文件的内容复制到源文件中,然后将结果视为您一直编写的文件。您需要注意这一点,因为名为一个定义规则 (ODR) 的 C++ 策略毫不奇怪地指出,每个函数和类最多应该有一个定义。这意味着如果你在某处声明一个类,那么该类的所有成员函数要么根本不定义,要么在一个文件中只定义一次。有一些例外(我会在一分钟内讨论),但现在只需将此规则视为硬性规定、无例外规则。
如果您采用非模板类并将类定义和实现都放入头文件中,您可能会遇到单一定义规则的问题。特别是,假设我编译了两个不同的 .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
那么您会破坏 ODR,因为成员函数未标记为内联,并且如果多个客户端 #include 此文件将有多个 MyClass::DoSomething 定义。
总结一下 - 您可能应该将您的类拆分为 .h/.cpp 对以避免破坏 ODR。但是,如果您正在编写类模板,则不需要 .cpp 文件(可能根本不应该有),如果您可以将类的每个成员函数标记为内联,您也可以避免使用 .cpp 文件。