sep*_*oad 31 c++ declaration definition header-files
当我们用Java,Vala或C#设计类时,我们将定义和声明放在同一个源文件中.但在C++中,传统上优先将两个或多个文件中的定义和声明分开.
如果我只使用头文件并将所有内容放入其中,如Java,会发生什么?是否存在性能损失?
tem*_*def 54
答案取决于你正在创造什么样的课程.
C++的编译模型可以追溯到C的时代,因此它将数据从一个源文件导入另一个源文件的方法比较原始.该#include指令从字面上将您所包含文件的内容复制到源文件中,然后将结果视为您一直编写的文件.您需要注意这一点,因为C++策略称为一个定义规则(ODR),毫不奇怪地说,每个函数和类最多只能有一个定义.这意味着如果在某个地方声明一个类,那么该类的所有成员函数应该根本没有定义,或者只在一个文件中定义一次.有一些例外(我会在一分钟内得到它们),但是现在只需要将这条规则视为一条硬性的,无异常的规则.
如果您使用非模板类并将类定义和实现放入头文件中,则可能会遇到一个定义规则的问题.特别是,假设我编译了两个不同的.cpp文件,这两个文件都#include包含实现和接口.在这种情况下,如果我尝试将这两个文件链接在一起,链接器将发现每个文件都包含类的成员函数的实现代码的副本.此时,链接器将报告错误,因为您违反了一个定义规则:所有类的成员函数有两种不同的实现.
为了防止这种情况,C++程序员通常将类拆分为头文件,该头文件包含类声明及其成员函数的声明,而不实现这些函数.然后将实现放入单独的.cpp文件中,该文件可以单独编译和链接.这允许您的代码避免遇到ODR问题.这是如何做.首先,每当您#include将类头文件转换为多个不同的.cpp文件时,每个文件只获取成员函数声明的副本,而不是它们的定义,因此您的类的客户端都不会最终得到定义.这意味着任何数量的客户端都可以使用#include头文件而不会在链接时遇到麻烦.由于您自己的带有实现的.cpp文件是包含成员函数实现的唯一文件,因此在链接时您可以将其与任意数量的其他客户端对象文件合并而不会有麻烦.这是将.h和.cpp文件拆分的主要原因.
当然,ODR有一些例外.第一个是模板函数和类.ODR明确声明您可以为同一模板类或函数提供多个不同的定义,前提是它们都是等效的.这主要是为了使编译模板更容易 - 每个C++文件都可以实例化相同的模板而不会与任何其他文件冲突.出于这个原因,以及其他一些技术原因,类模板往往只有一个没有匹配的.cpp文件的.h文件.任何数量的客户端都可以#include毫无困难地存档.
ODR的另一个主要例外涉及内联函数.该规范明确指出ODR不适用于内联函数,因此如果您的头文件具有标记为内联的类成员函数的实现,那就完全没问题了.在#include不破坏ODR的情况下,此文件可以包含任意数量的文件.有趣的是,在类的主体中声明和定义的任何成员函数都是隐式内联的,所以如果你有一个像这样的头:
#ifndef Include_Guard
#define Include_Guard
class MyClass {
public:
void DoSomething() {
/* ... code goes here ... */
}
};
#endif
Run Code Online (Sandbox Code Playgroud)
然后你不会冒破坏ODR的风险.如果你重写为
#ifndef Include_Guard
#define Include_Guard
class MyClass {
public:
void DoSomething();
};
void MyClass::DoSomething() {
/* ... code goes here ... */
}
#endif
Run Code Online (Sandbox Code Playgroud)
然后你会破坏ODR,因为成员函数没有标记为内联,如果多个客户端#include这个文件将有多个定义MyClass::DoSomething.
总而言之 - 您应该将类拆分为.h/.cpp对,以避免破坏ODR.但是,如果您正在编写类模板,则不需要.cpp文件(可能根本不应该有).如果您可以标记类内联的每个成员函数,那么您也可以避免使用.cpp文件.
将定义放在头文件中的缺点如下: -
头文件A - 包含metahodA()的定义
头文件B - 包括头文件A.
现在让我们说你改变了methodA的定义.您需要编译文件A和B,因为在B中包含头文件A.
| 归档时间: |
|
| 查看次数: |
19135 次 |
| 最近记录: |