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

първо, искам да кажа, че съм наясно, че този тип въпроси са задавани и преди (напр. тук Разрешаване на кръгова зависимост между шаблонни класове).

Това решение обаче (Разделяне на декларацията от изпълнението) работи само когато поставите двата класа в един файл. В моя случай имам 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 # грешка Никога не използвайте ‹bits/mathdef.h› директно; включете ‹math.h› вместо #endif - person Gem Taylor; 14.12.2017