Отдельная компиляция в двух словах
Во-первых, давайте рассмотрим несколько быстрых примеров:
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
Включение охранников - это хак, чтобы обойти отсутствие реальной системы модулей, превращая заголовки в своего рода ограниченный модуль; с этой целью включение одного и того же заголовка более одного раза не должно оказывать неблагоприятного воздействия.
Включите работу охраны, сделав последующие #include no-ops с определениями, доступными из первого include. Из-за их ограниченного характера макросы, управляющие параметрами заголовков, должны быть согласованными по всему проекту (странные заголовки, такие как вызывают проблемы), а все #include открытых заголовков должны находиться вне любого пространства имен, класса и т. Д. верх любого файла.
См. Мой include guard рекомендации по именованию , включая короткую программу для генерации include guard .
Объявления
Классы , функции , объекты и шаблоны могут быть объявлены практически где угодно, могут быть объявлены любое количество раз, и должен быть объявлен перед тем, как ссылаться на них. В некоторых странных случаях вы можете объявлять классы по мере их использования; не буду освещать это здесь.
Определения
Классы могут быть определены не более одного раза [1] на TU; это обычно происходит, когда вы включаете заголовок для определенного класса. Функции и объекты должны быть определены один раз ровно в одном TU; это обычно происходит, когда вы реализуете их в файле **. cpp *. Однако встроенные функции , включая неявно встроенные функции внутри определений классов, могут быть определены в нескольких TU, но определения должны быть идентичными.
Для практических целей [2] , шаблоны (как шаблоны классов, так и шаблоны функций) определены только в заголовках, и если вы хотите использовать отдельный файл, используйте другой заголовок [3] .
[1] Из-за ограничения не более одного раза заголовки используют защитные устройства для предотвращения многократного включения и, следовательно, множественных ошибок определения.
[2] Я не буду здесь останавливаться на других возможностях.
[3] Назовите его blahblah_detail.hpp , blahblah_private.hpp или аналогичным, если вы хотите документально подтвердить, что он не является общедоступным.
Руководство
Итак, хотя я уверен, что все вышеперечисленное - это пока что большой грязный шарик, это всего лишь страница с описанием того, что должно занимать несколько глав, поэтому используйте его в качестве краткого справочника. Понимание понятий выше, однако, важно. Используя их, вот краткий список рекомендаций (но не абсолютных правил):
- Всегда заголовки имен последовательно в одном проекте, например **. H * для C и **. Hpp * для C ++.
- Никогда не содержит файл, который не является заголовком.
- Всегда именуют файлы реализации (которые будут скомпилированы напрямую), такие как **. C * и **. Cpp *.
- Используйте систему сборки , которая может автоматически компилировать ваши исходные файлы. make является каноническим примером, но есть много альтернатив. Сохраняйте это простым в простых случаях. Например, make может использовать свои встроенные правила и даже без make-файла.
- Используйте систему сборки, которая может генерировать зависимости заголовка. Некоторые компиляторы могут генерировать это с помощью переключателей командной строки, таких как -M , так что вы можете легко создать удивительно полезную систему .
Процесс сборки
(Вот небольшая часть, которая отвечает на ваш вопрос, но вам нужно большинство из вышеперечисленного, чтобы попасть сюда.)
При сборке система сборки будет проходить несколько этапов, из которых важными для этого обсуждения являются:
- компилировать каждый файл реализации в виде TU, создавая объектный файл (**. O *, **. Obj *)
- каждый компилируется независимо от других, поэтому каждый TU нуждается в объявлениях и определениях
- связывает эти файлы вместе с указанными библиотеками в один исполняемый файл
Я рекомендую вам изучить основы make, поскольку она популярна, понятна и с ней легко начать. Однако это старая система с несколькими проблемами, и в какой-то момент вы захотите переключиться на что-то другое.
Выбор системы сборки - это почти религиозный опыт, подобный выбору редактора, за исключением того, что вам придется работать с большим количеством людей (все работают над одним и тем же проектом) и, вероятно, будут намного более ограничены прецедентом и соглашением. Вы можете использовать IDE, которая обрабатывает те же детали для вас, но это не дает никакой реальной выгоды от использования комплексной системы сборки, и вы действительно должны знать, что она делает изнутри.
Шаблоны файлов
* +1135 * example.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
example.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.
}