简而言之单独编译
首先,让我们举一些简单的例子:
struct ClassDeclaration; // 'class' / 'struct' mean almost the same thing here
struct ClassDefinition {}; // the only difference is default accessibility
// of bases and members
void function_declaration();
void function_definition() {}
extern int global_object_declaration;
int global_object_definition;
template<class T> // cannot replace this 'class' with 'struct'
struct ClassTemplateDeclaration;
template<class T>
struct ClassTemplateDefinition {};
template<class T>
void function_template_declaration();
template<class T>
void function_template_definition() {}
翻译单位
A 翻译单位(TU) 是单个源文件(应该是 **.cpp* 文件)及其包含的所有文件,以及它们包含的等等。换句话说:预处理单个文件的结果。
Headers
包含防护是解决缺乏真正的模块系统的一种技巧,将标头变成一种有限的模块;为此,多次包含相同的标头一定不会产生不利影响。
包含防护通过使后续 #includes 不执行任何操作来工作,并使用第一个包含中可用的定义。由于其有限的性质,控制标头选项的宏应该在整个项目中保持一致(像 这样的奇怪标头会导致问题),并且公共标头的所有 #include 都应该位于任何命名空间、类等之外,通常位于任何文件的顶部。
查看我的包含守卫命名建议 https://stackoverflow.com/questions/1744144/adding-ifndef-define-endif-breaks-the-compile/1744302#1744302,包括一个简短的程序生成包含守卫 http://bitbucket.org/kniht/scraps/src/tip/python/includeguard.py.
声明
Classes, 功能, objects, and 模板几乎可以在任何地方声明,可以声明任意次数,并且must在以任何方式引用它们之前进行声明。在一些奇怪的情况下,您可以在使用类时声明它们;这里不会介绍这个。
定义
Classes may be defined at most once[1] per TU; this typically happens when you include a header for a particular class. Functions and objects must be defined once in exactly one TU; this typically happens when you implement them in a **.cpp* file. However, inline functions, including implicitly inline functions inside class definitions, may be defined in multiple TUs, but the definitions must be identical.
For practical purposes[2], templates (both class templates and function templates) are defined only in headers, and if you want to use a separate file, then use another header[3].
[1] Because of the at-most-once restriction, headers use include guards to prevent multiple inclusion and thus multiple definition errors.
[2] I won't cover the other possibilities here.
[3] Name it blahblah_detail.hpp, blahblah_private.hpp, or similar if you want to document that it's non-public.
指南
因此,虽然我确信到目前为止以上所有内容都是一团泥,但它还不到一页,应该占据几章的内容,因此请将其用作简要参考。然而,理解上述概念很重要。使用这些,这里有一个简短的指南列表(但不是绝对规则):
-
Always在单个项目中一致地命名标头,例如 C 的 **.h* 和 C++ 的 **.hpp*。
-
Never包含一个不是标头的文件。
-
Always一致地命名实现文件(将直接编译),例如 **.c* 和 **.cpp*。
- Use a 构建系统它可以自动编译您的源文件。make是典型的例子,但还有很多替代方案。在简单的情况下保持简单。例如,make 可以使用其内置规则,甚至无需 makefile。
- 使用可以生成标头依赖项的构建系统。一些编译器可以使用命令行开关生成它,例如-M,所以你可以做一个非常有用的系统 http://bitbucket.org/kniht/scraps/src/tip/cpp/Makefile.common easily.
构建过程
(这里只是回答你的问题,但你需要上面的大部分内容才能到达这里。)
当您构建时,构建系统将经历几个步骤,其中本次讨论的重要步骤是:
- compile each implementation file as a TU, producing an object file (**.o*, **.obj*)
- 每个都被编译独立地其他的,这就是为什么每个 TU 都需要声明和定义
- 将这些文件与指定的库一起链接到单个可执行文件中
我建议您学习 make 的基础知识,因为它很流行、易于理解且易于入门。然而,它是一个存在一些问题的旧系统,您可能会在某个时候想要切换到其他系统。
选择构建系统几乎是一种宗教体验,就像选择编辑器一样,只不过您必须与更多的人合作(每个人都在同一个项目上工作),并且可能会受到先例和惯例的更多限制。您可以使用一个 IDE 来为您处理相同的细节,但是使用全面的构建系统并没有真正的好处,而且您确实应该知道它在幕后做了什么。
文件模板
示例.hpp
#ifndef EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75
#define EXAMPLE_INCLUDE_GUARD_60497EBE580B4F5292059C8705848F75
// all project-specific macros for this project are prefixed "EXAMPLE_"
#include <ostream> // required headers/"modules"/libraries from the
#include <string> // stdlib, this project, and elsewhere
#include <vector>
namespace example { // main namespace for this project
template<class T>
struct TemplateExample { // for practical purposes, just put entire
void f() {} // definition of class and all methods in header
T data;
};
struct FooBar {
FooBar(); // declared
int size() const { return v.size(); } // defined (& implicitly inline)
private:
std::vector<TemplateExample<int> > v;
};
int main(std::vector<std::string> args); // declared
} // example::
#endif
示例.cpp
#include "example.hpp" // include the headers "specific to" this implementation
// file first, helps make sure the header includes anything it needs (is
// independent)
#include <algorithm> // anything additional not included by the header
#include <iostream>
namespace example {
FooBar::FooBar() : v(42) {} // define ctor
int main(std::vector<std::string> args) { // define function
using namespace std; // use inside function scope, if desired, is always okay
// but using outside function scope can be problematic
cout << "doing real work now...\n"; // no std:: needed here
return 42;
}
} // example::
main.cpp
#include <iostream>
#include "example.hpp"
int main(int argc, char const** argv) try {
// do any global initialization before real main
return example::main(std::vector<std::string>(argv, argv + argc));
}
catch (std::exception& e) {
std::cerr << "[uncaught exception: " << e.what() << "]\n";
return 1; // or EXIT_FAILURE, etc.
}
catch (...) {
std::cerr << "[unknown uncaught exception]\n";
return 1; // or EXIT_FAILURE, etc.
}