Циклическая зависимость шаблона - отдельные классы в разных файлах

Прежде всего, я хочу сказать, что мне известно, что этот вопрос задавался раньше (например, здесь Устранение круговой зависимости между классами шаблонов).

Однако это решение (отделение объявления от реализации) работает только при помещении обоих классов в один файл. В моем случае у меня есть StateManager и State класс, оба из которых довольно большие и гарантированно будут расти. Таким образом, наличие их в одном большом файле мне кажется неудовлетворительным.

Вот несколько важных фрагментов кода:

// Forward declare the StateManager --> does not work (incomplete type)
class State
{
public:
    template <class TData>
    void RequestStackPush(ID stateId, std::shared_ptr<TData> data);
private:
    StateManager & stataManager;
}

Здесь реализация метода RequestStackPush()

template<class TData>
    inline void State::RequestStackPush(ID stateId, std::shared_ptr<TData> data)
    {
        // Uses the state manager's PushState() method - here the issue with the incomplete type arises
        stateManager.PushState<TData>(stateId, data);
    }

Очевидно, StateManager все время использует класс State. Он создает методы вызова и т. Д., Поэтому предварительное объявление здесь не является решением. Просто чтобы дать вам пример:

template<class TData>
    inline void StateManager::PushState(State::ID stateId, std::shared_ptr<TData> data)
    {
        std::unique_ptr<BasePendingChange> pendingChange = std::make_unique<PendingPushDataChange<TData>>(Push, stateId, data);
        pendingChangeQueue.push(std::move(pendingChange));
    }

В настоящее время оба класса находятся в одном большом файле. Во-первых, объявление класса State с объявлением вперед StateManager, за которым следует объявление класса StateManager, за которым следует реализация описанного выше метода State::RequestStackPush() и, наконец, реализация всех методов шаблона StateManager.

Как я могу разделить это на два разных файла?


person Adrian Albert Koch    schedule 14.12.2017    source источник
comment
Рассматривали ли вы простое удаление ссылки StateManager и метода RequestStackPush? Насколько плохо это было бы для остальной части вашего кода?   -  person Sebastian Redl    schedule 14.12.2017
comment
Плохо, так как это важно для возможности передачи любых данных в производное состояние. Отправка состояния запроса необходима для того, чтобы задержать создание состояния отправки. Создание состояния при обновлении или рендеринге стека состояний может привести к ошибкам.   -  person Adrian Albert Koch    schedule 14.12.2017
comment
Я имел в виду, почему люди не могут использовать StateManager :: PushState напрямую?   -  person Sebastian Redl    schedule 14.12.2017
comment
@SebastianRedl Поскольку, как я уже сказал, это может или фактически прервет цикл обновления, в котором зацикливается стек состояний   -  person Adrian Albert Koch    schedule 14.12.2017


Ответы (1)


Однако это решение (отделение объявления от реализации) работает только при помещении обоих классов в один файл.

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

Итак, в одном файле:

  • Объявить StateManager
  • Определить State
  • Включить определение StateManager
  • Определите функции-члены, которые зависят от определения StateManager

В другом файле ничего необычного:

  • Включить определение State
  • Определите StateManager и его функции-члены.

Конечным результатом является то, что включение любого заголовка дает одни и те же определения и объявления в требуемом порядке. Таким образом, это разделение файлов никоим образом не помогает ограничить объем повторной компиляции, вызванной изменением одного из заголовков.

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

person eerorika    schedule 14.12.2017
comment
Ему нужно полное определение State перед определением StateManager, поскольку некоторые сигнатуры функций ссылаются на внутренние типы. Но общая идея верна. - person Sebastian Redl; 14.12.2017
comment
@SebastianRedl а, я не заметил, что зависимость была в споре. Исправление ... Исправлено. - person eerorika; 14.12.2017
comment
@AdrianKoch State.h не должен включать StateManager.h до определения State. В противном случае существует круговая зависимость. Вы должны избавиться от этого включения. Невозможно сказать, как это сделать, без минимального воспроизводимого примера. - person eerorika; 14.12.2017
comment
Другой трюк, который вы можете использовать в каждом дочернем заголовке, - это утверждение, что родительский заголовок загружен, поэтому ваш порядок сохраняется. Стандартные заголовки полны примеров, например: #if! Defined _MATH_H &&! Defined _COMPLEX_H # error Никогда не используйте ‹bits / mathdef.h› напрямую; включите ‹math.h› вместо #endif - person Gem Taylor; 14.12.2017