Я видел некоторый код, в котором разработчик определил шаблон класса в файле .h и определил его методы в файле .hpp. Это несколько удивило меня.
Существуют ли в C ++ особые соглашения при работе с шаблонами и в каких файлах они должны быть?
Например, скажем, у меня был шаблон класса Vector
с методами для векторных операций (сложение, вычитание, точка и т. Д.). Я также хотел бы специализировать определенные функции, если аргумент шаблона представляет собой float
(операторы сравнения). Как бы вы разделяли все это между файлами (укажите, есть ли .h, .hpp, .cpp).
Обычно (по моему опыту, YMMV) файл hpp
является файлом CPP с редакцией #include
. Это делается для того, чтобы разбить код на два физических файла, основной включаемый файл и файл с подробностями реализации, о которых пользователям вашей библиотеки не нужно знать. Это делается так:
#include
) template<...> class MyGizmo
{
public:
void my_fancy_function();
};
#include "super_lib_implementation.hpp"
#include
это напрямую ) template<...> void MyGizmo<...>::my_fancy_function()
{
// magic happens
}
Мне кажется, что это запутанный способ разделения кода. .h
обозначает header, а .hpp
- C++ header обычно. Помещение определений шаблонов в .hpp
, а другого кода в .h
кажется злоупотреблением расширением файла.
Шаблонный код обычно пишется в одном заголовке вместе с объявлением шаблона, или в другом заголовке, который также может иметь специальный суффикс, например .tcc
или что-то подобное, а затем включается в заголовок, в который помещены объявления шаблонов. Но на самом деле, расширение файла не имеет значения, если вы последовательны в своем проекте.
Исключение составляют случаи, когда вы используете явное инстанцирование и точно знаете, какие инстанцирования вам понадобятся. Представьте, что у вас есть шаблон и ровно два его инстанса:
template<typename T>
struct SymbolTable {
T *lookup();
// ...
};
template struct SymbolTable<GlobalSym>;
template struct SymbolTable<LocalSym>;
Вам не нужно помещать определение lookup
и другие в заголовок. Вы можете поместить их все в файл .cpp
, вместе с двумя явными директивами инстанцирования.
Последний пункт, о котором вы спрашиваете, относится к другой теме: Явные специализации. Я рекомендую вам задать отдельный вопрос на эту тему. В двух словах, явные определения специализации, где все аргументы шаблона имеют конкретные значения/типы, должны быть помещены в файл .cpp
, но их декларации должны быть помещены в заголовок (чтобы сообщить другим, что эти определенные члены являются специализированными).
Я никогда не слышал, чтобы объявления классов помещались в .h
, а определения шаблонов - в .hpp
. В каждом проекте, который я видел, использовался подход, согласно которому .h
и .hpp
означают одно и то же, и вы должны стандартизировать его (обычно .h
).
Шаблонные методы можно поместить в конец файла .h
или в отдельные файлы -inl.h
(как предлагает Google Руководство по стилю C ++ , например).
Если мы определим шаблон класса в файле .h и определим его методы в файле .hpp , то мы должны #include
Файл .hpp в файле .h. Проще просто определить методы в конце файла .h (тогда файл .hpp не нужен).
Я думаю, что одним из аспектов разделения файла интерфейса (.h) и файла реализации (.hpp) является то, что пользователю шаблона (то есть другому разработчику) нужно только посмотреть на файл .h, чтобы понять, как использовать шаблон, не отвлекаясь на его реальную реализацию.
Для меня это звучит необычно. Определение шаблона и все специализации должны быть скомпилированы вместе с его объявлением, за исключением экспортных
шаблонов, функции которой фактически не существует.
C ++ 0x действительно вводит объявления шаблонов extern
, которые позволяют вам определять явные специализации в другом исходном файле (единицах перевода). Это уже существует как расширение в GCC и, возможно, на других платформах.
Разделение на два файла может помочь немного «спрятать» реализацию или, возможно, допустить какую-то лень с doxygen.
Ага! Также возможно улучшить время компиляции с помощью предварительно скомпилированных заголовков. Компилятор может кэшировать заголовки для каждого файла. После этого можно было бы изменить отдельный заголовок «реализация», не касаясь заголовка «интерфейса». Но не наоборот, заголовок реализации по-прежнему будет занимать большую часть времени компиляции, и выигрыш будет очень хрупким и зависеть от платформы и конкретных внесенных изменений. В конце концов, PCH сокращает время работы с несколькими исходными файлами, а оптимизация зависимостей заголовков бессмысленна.