Ответ зависит от того, какой класс вы создаете.
Модель компиляции 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
тогда будет прерывать ODR, так как функция-член не помечена как встроенная, и если несколько клиентов #include
, этот файл будет иметь несколько определений MyClass::DoSomething
.
Итак, подведем итог - вам, вероятно, следует разделить ваши классы на пару .h / .cpp, чтобы избежать нарушения ODR. Однако, если вы пишете шаблон класса, вам не нужен файл .cpp (и, вероятно, не должен иметь его вообще), и если вы в порядке, помечая каждую функцию-член вашего класса встроенной, вы также можете избегайте .cpp файла.