У меня есть несколько классов шаблонов, которые разделены на файлы заголовков и файлы реализации:
// MyTemplate.h
template<class T>
class MyTemplate {
public:
void Foo();
};
// MyTemplate.cpp
#include "MyTemplate.h"
template<class T>
void MyTemplate<T>::Foo() {
// ...
}
Чтобы использовать MyTemplate
в другой единице перевода, я могу #include
файл реализации:
#include "MyTemplate.cpp"
template class MyTemplate<int>;
Таким образом я получаю:
Я нахожусь в процессе преобразования в модули С++. Есть ли способ добиться того же с модулями, когда я больше не должен использовать #include
?
Примечание: вы можете добиться того же, поместив реализацию методов в заголовок после определения класса. Это довольно распространенная практика, тогда как #включение cpp-файла — нет.
Отвечает ли это на ваш вопрос? Почему шаблоны можно реализовать только в заголовочном файле?
Добавление template class MyTemplate<int>;
после включения реализации должно быть излишним. template class MyTemplate<int>;
можно поместить внутрь .cpp
файла, если набор типов T
известен заранее.
Опасность использования файлов cpp заключается в том, что когда-нибудь в будущем кто-то — возможно, вы — может добавить файлы cpp в файл проекта (makefile и т. д.). Сейчас это может не показаться проблемой, но код может работать дольше, чем вы ожидаете (скажем, в проектах, в то время, когда вы забыли, что файлы cpp были включены). Результатом часто будет проект, который компилируется, но не компонуется из-за нескольких определений. Если вам действительно нужны определения в отдельном файле (и вы не можете использовать современные возможности, такие как модули), используйте другое расширение (например, .imp) и #include
это.
@Peter, если бы он работал как включаемый файл, я думаю, он работал бы без проблем при компиляции, точно так же, как его включает пустой файл. (не сказать, что это хорошая практика, но я не вижу, как это сломается)
@appleapple Я не говорил, что при компиляции произойдет сбой. Я сказал, что это может привести к сбою при связывании.
@Peter, да, я имею в виду, что он будет скомпилирован и слинкован.
В модулях это тривиально делается простым созданием раздела интерфейса. Это должен быть раздел интерфейса, потому что определение шаблона по-прежнему является частью вашего интерфейса, поэтому оно должно быть видно тем, кто его импортирует (точно так же, как обычный C++).
Это делается следующим образом:
//Primary interface module
export module MyTemplates;
export import :TemplateDef;
//Other stuff for your module.
//Definition for your template.
export module MyTemplates:TemplateDef;
import :TemplateDecl;
template<class T>
export void MyTemplate<T>::Foo() {
// ...
}
//Declaration for your template
export module MyTemplates:TemplateDecl;
template<class T>
export class MyTemplate {
public:
void Foo();
};
При этом, поскольку определения должны быть доступны всем, мало что можно получить, разделив их на два файла. Если вы измените файл определения, все, кто импортирует ваш модуль, все равно должны быть перекомпилированы. Именно так работают шаблоны.
Обратите внимание, что немодульная форма вашего кода довольно плоха. Отделить реализацию шаблона от интерфейса — это нормально; делать это с файлом с именем «.cpp» нельзя. Заголовки должны быть включены; вот почему мы называем их «заголовки». Предположение, которое сделает пользователь, увидев файл с расширением «.cpp», состоит в том, что он не должен быть включен.
Просто используйте расширение «.h» с обоими. ".hxx" также часто используется для реализации (которая должна быть включена в версию ".h").
Ах! Это умно :) Означает ли это, что компилятор сможет встраивать функции, реализованные в TemplateDef
, или они должны быть реализованы в TemplateDecl
, чтобы встраивание работало?
Разделы модуля @MagnarMyrtveit должны быть скомпилированы до их основного модуля интерфейса модуля. Блок интерфейса первичного модуля скомпилирован в BMI, который подобен абстрактному синтаксическому дереву. Короче говоря: при компиляции вашего «основного» файла компилятор имеет доступ к абстрактному синтаксическому дереву шаблонного класса, так что да, он может встроить то, что хочет. Но это, вероятно, зависит от того, поскольку разные компиляторы реализуют модули по-разному. Если вы действительно хотите встраивание между единицами перевода, безопаснее всего использовать LTO (оптимизация времени компоновки).
Или вы можете скомпилировать простой пример и проверить сгенерированную сборку с флагом -S
(зависит от компилятора), чтобы увидеть, что встроено, а что нет. Зависит от нескольких факторов, таких как то, как компилятор реализует модули и что он с ними делает, какие флаги оптимизации вы ему задаете, а также некоторые эвристики встраивания. Я не пробовал это, поэтому не могу точно сказать, что происходит.
вы не должны называть это
.cpp
, чтобы его не путали с реальными исходными файлами.#include foo.cpp
обычно не идет