Dependență circulară de șablon - clase separate în fișiere diferite

în primul rând, vreau să spun că sunt conștient de faptul că acest tip de întrebare a mai fost pus (de exemplu aici Rezolvarea unei dependențe circulare între clasele șabloane).

Cu toate acestea, această soluție (separarea declarației de implementare) funcționează numai atunci când puneți ambele clase într-un singur fișier. În cazul meu, am un StateManager și o clasă State, ambele fiind destul de mari și garantate că vor crește. Astfel, a le avea într-un singur fișier mare mi se pare nesatisfăcător.

Iată câteva fragmente de cod importante:

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

Aici implementarea metodei 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);
    }

Evident, StateManager folosește clasa State tot timpul. Creează că apelează metode etc., așa că declarația înainte nu este o soluție aici. Doar ca să-ți dau un exemplu:

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));
    }

În prezent, ambele clase sunt într-un singur fișier mare. În primul rând, declararea clasei State cu StateManager fiind declarată înainte urmată de declararea clasei StateManager urmată de implementarea metodei State::RequestStackPush() descrise mai sus și în final implementarea tuturor metodelor șablon StateManager.

Cum pot separa acest lucru în două fișiere diferite?


person Adrian Albert Koch    schedule 14.12.2017    source sursă
comment
V-ați gândit să eliminați pur și simplu referința StateManager și metoda RequestStackPush? Cât de rău ar fi asta pentru restul codului tău?   -  person Sebastian Redl    schedule 14.12.2017
comment
Rău, deoarece este esențial pentru a putea transmite orice date unui stat derivat. Request State Push este necesar pentru a întârzia crearea stării pushed. Crearea unei stări în timpul actualizării sau redării stivei de stări poate duce la erori   -  person Adrian Albert Koch    schedule 14.12.2017
comment
Am vrut să spun, de ce oamenii nu pot folosi StateManager::PushState direct?   -  person Sebastian Redl    schedule 14.12.2017
comment
@SebastianRedl Deoarece, așa cum am spus, acest lucru poate sau de fapt va întrerupe bucla de actualizare în care stiva de stare este în buclă   -  person Adrian Albert Koch    schedule 14.12.2017


Răspunsuri (1)


Cu toate acestea, această soluție (separarea declarației de implementare) funcționează numai atunci când puneți ambele clase într-un singur fișier.

Nu, nu funcționează doar într-un singur fișier. Puteți construi oricând un fișier identic incluzând sub-anteturi. Trebuie doar să faceți ceva care ar fi neobișnuit cu non-șabloane (deși aceeași tehnică funcționează cu toate definițiile de funcții inline): Trebuie să includeți un fișier după definirea clasei. Anteturile nu se limitează la a fi în partea de sus a fișierului, în ciuda numelui care le este dat.

Deci, într-un singur fișier:

  • Declara StateManager
  • Definiți State
  • Includeți definiția lui StateManager
  • Definiți funcțiile membre care depind de definiția lui StateManager

Nimic neobișnuit în celălalt fișier:

  • Includeți definiția lui State
  • Definiți StateManager și funcțiile sale membre.

Rezultatul final este că includerea oricărui antet produce aceleași definiții și declarații în ordinea necesară. Deci, această împărțire a fișierelor nu ajută în niciun fel la limitarea cantității de recompilări cauzate de modificarea unuia dintre anteturi.

Poate fi o chestiune de gust, dar includ întotdeauna definițiile cerute de funcțiile inline (inclusiv membrii șabloanelor și funcțiilor șablon) după definirea clasei. În acest fel, nu trebuie să-mi fac griji dacă este necesar să fac acest lucru.

person eerorika    schedule 14.12.2017
comment
El are nevoie de definiția completă a lui State înainte de definiția lui StateManager, deoarece unele semnături de funcție fac referire la tipurile interne. Dar ideea generală este corectă. - person Sebastian Redl; 14.12.2017
comment
@SebastianRedl ah, nu am observat că dependența era în discuție. Remediere... Remediată. - person eerorika; 14.12.2017
comment
@AdrianKoch State.h nu trebuie să includă StateManager.h înainte ca State să fie definit. În caz contrar, există o dependență circulară. Trebuie să scapi de acest include. Nu se poate spune cum, fără un exemplu minim reproductibil. - person eerorika; 14.12.2017
comment
Celălalt truc pe care l-ați putea folosi în fiecare antet copil este să afirmați că antetul părinte a fost încărcat, astfel încât comanda dvs. a fost menținută. Antetele standard sunt pline de exemple precum: #if !defined _MATH_H && !defined _COMPLEX_H # eroare Nu utilizați niciodată ‹bits/mathdef.h› direct; includeți ‹math.h› în loc de #endif - person Gem Taylor; 14.12.2017