Шаблон класса с интерфейсом и реализацией в отдельных файлах

У меня есть несколько классов шаблонов, которые разделены на файлы заголовков и файлы реализации:

// 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>;

Таким образом я получаю:

  1. Держите интерфейс и реализацию в отдельных файлах.
  2. Не нужно поддерживать список всех экземпляров шаблона в файле реализации.

Я нахожусь в процессе преобразования в модули С++. Есть ли способ добиться того же с модулями, когда я больше не должен использовать #include?

вы не должны называть это .cpp, чтобы его не путали с реальными исходными файлами. #include foo.cpp обычно не идет

463035818_is_not_a_number 19.04.2023 16:13

Примечание: вы можете добиться того же, поместив реализацию методов в заголовок после определения класса. Это довольно распространенная практика, тогда как #включение cpp-файла — нет.

wohlstad 19.04.2023 16:13

Отвечает ли это на ваш вопрос? Почему шаблоны можно реализовать только в заголовочном файле?

Chris 19.04.2023 16:18

Добавление template class MyTemplate<int>; после включения реализации должно быть излишним. template class MyTemplate<int>; можно поместить внутрь .cpp файла, если набор типов T известен заранее.

Evg 19.04.2023 16:22

Опасность использования файлов cpp заключается в том, что когда-нибудь в будущем кто-то — возможно, вы — может добавить файлы cpp в файл проекта (makefile и т. д.). Сейчас это может не показаться проблемой, но код может работать дольше, чем вы ожидаете (скажем, в проектах, в то время, когда вы забыли, что файлы cpp были включены). Результатом часто будет проект, который компилируется, но не компонуется из-за нескольких определений. Если вам действительно нужны определения в отдельном файле (и вы не можете использовать современные возможности, такие как модули), используйте другое расширение (например, .imp) и #include это.

Peter 19.04.2023 16:23

@Peter, если бы он работал как включаемый файл, я думаю, он работал бы без проблем при компиляции, точно так же, как его включает пустой файл. (не сказать, что это хорошая практика, но я не вижу, как это сломается)

apple apple 19.04.2023 16:39

@appleapple Я не говорил, что при компиляции произойдет сбой. Я сказал, что это может привести к сбою при связывании.

Peter 19.04.2023 17:01

@Peter, да, я имею в виду, что он будет скомпилирован и слинкован.

apple apple 19.04.2023 17:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В модулях это тривиально делается простым созданием раздела интерфейса. Это должен быть раздел интерфейса, потому что определение шаблона по-прежнему является частью вашего интерфейса, поэтому оно должно быть видно тем, кто его импортирует (точно так же, как обычный 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, чтобы встраивание работало?

Magnar Myrtveit 20.04.2023 07:26

Разделы модуля @MagnarMyrtveit должны быть скомпилированы до их основного модуля интерфейса модуля. Блок интерфейса первичного модуля скомпилирован в BMI, который подобен абстрактному синтаксическому дереву. Короче говоря: при компиляции вашего «основного» файла компилятор имеет доступ к абстрактному синтаксическому дереву шаблонного класса, так что да, он может встроить то, что хочет. Но это, вероятно, зависит от того, поскольку разные компиляторы реализуют модули по-разному. Если вы действительно хотите встраивание между единицами перевода, безопаснее всего использовать LTO (оптимизация времени компоновки).

alexpanter 21.04.2023 16:04

Или вы можете скомпилировать простой пример и проверить сгенерированную сборку с флагом -S (зависит от компилятора), чтобы увидеть, что встроено, а что нет. Зависит от нескольких факторов, таких как то, как компилятор реализует модули и что он с ними делает, какие флаги оптимизации вы ему задаете, а также некоторые эвристики встраивания. Я не пробовал это, поэтому не могу точно сказать, что происходит.

alexpanter 21.04.2023 16:06

Другие вопросы по теме