Ответ 1
Ответ зависит от того, какой класс вы создаете.
Модель компиляции С++ восходит к дням C, поэтому его метод импорта данных из одного исходного файла в другой является сравнительно примитивным. Директива #include
буквально копирует содержимое файла, который вы включаете в исходный файл, затем обрабатывает результат так, как если бы это был файл, который вы написали. Вы должны быть осторожны в этом из-за политики С++, называемой одним правилом определения (ODR), в котором, как ни удивительно, говорится, что каждая функция и класс должны иметь не более одного определения. Это означает, что если вы объявляете класс где-то, все функции этого класса должны быть либо не определены вообще, либо точно определены один раз в одном файле. Есть некоторые исключения (я доберусь до них через минуту), но сейчас просто рассматривайте это правило так, как если бы это было жесткое правило без исключений.
Если вы возьмете класс без шаблона и поместите определение класса и реализацию в заголовочный файл, вы можете столкнуться с проблемой с одним правилом определения. В частности, предположим, что у меня есть два разных файла .cpp, которые я компилирую, оба из которых #include
содержат ваш заголовок, содержащий как реализацию, так и интерфейс. В этом случае, если я попытаюсь связать эти два файла вместе, компоновщик обнаружит, что каждый из них содержит копию кода реализации для функций класса. На этом этапе компоновщик сообщит об ошибке, поскольку вы нарушили одно правило определения: существуют две различные реализации всех функций-членов класса.
Чтобы предотвратить это, программисты на C++ обычно разбивают классы на заголовочный файл, содержащий объявление класса, а также декларации его функций-членов без реализации этих функций. Затем реализации помещаются в отдельный файл .cpp, который может быть скомпилирован и связан отдельно. Это позволяет вашему коду избежать проблем с ODR. Вот как. Во-первых, всякий раз, когда вы #include
заголовок файла класса в несколько разных файлов .cpp, каждый из них просто получает копию деклараций функций-членов, а не их определения, и поэтому ни один из ваших клиентов класса не получит определений, Это означает, что любое количество клиентов может #include
ваш заголовочный файл, не сталкиваясь с трудностями во время ссылки. Поскольку ваш собственный .cpp файл с реализацией является единственным файлом, который содержит реализации функций-членов, во время соединения вы можете объединить его с любым количеством других объектных файлов клиента без каких-либо проблем. Это основная причина, по которой вы разделяете файлы .h и .cpp.
Конечно, в ODR есть несколько исключений. Первая из них включает в себя функции и классы шаблонов. ODR явно заявляет, что вы можете иметь несколько разных определений для одного и того же класса или функции шаблона при условии, что все они эквивалентны. Это прежде всего упрощает сбор шаблонов - каждый файл С++ может создавать один и тот же шаблон без столкновения с другими файлами. По этой причине и еще несколько технических причин шаблоны классов имеют простой файл .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 файла.