Dépendance circulaire du modèle - classes séparées dans différents fichiers

tout d'abord, je tiens à dire que je suis conscient que ce genre de question a déjà été posé (par exemple ici Résoudre une dépendance circulaire entre les classes de modèles).

Cependant, cette solution (séparer la déclaration de l'implémentation) ne fonctionne que lorsque les deux classes sont placées dans un seul fichier. Dans mon cas, j'ai un StateManager et une classe State qui sont tous deux assez grands et dont la croissance est garantie. Ainsi, les avoir dans un seul gros fichier ne me semble pas satisfaisant.

Voici quelques extraits de code importants :

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

Ici l'implémentation de la méthode 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);
    }

Évidemment, le StateManager utilise la classe State tout le temps. Il crée des appels de méthodes, etc. donc la déclaration directe n'est pas une solution ici. Juste pour vous donner un exemple :

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

Actuellement, les deux classes sont dans un seul gros fichier. Tout d'abord, la déclaration de la classe State avec le StateManager déclaré en avant, suivie de la déclaration de la classe StateManager suivie de l'implémentation de la méthode State::RequestStackPush() décrite ci-dessus et enfin de l'implémentation de toutes les méthodes du modèle StateManager.

Comment puis-je séparer cela en deux fichiers différents ?


person Adrian Albert Koch    schedule 14.12.2017    source source
comment
Avez-vous envisagé de simplement supprimer la référence StateManager et la méthode RequestStackPush ? À quel point cela serait-il mauvais pour le reste de votre code ?   -  person Sebastian Redl    schedule 14.12.2017
comment
Mauvais car c'est crucial pour pouvoir transmettre des données à un État dérivé. Le Request State Push est nécessaire pour retarder la création de l’état poussé. La création d'un état lors de la mise à jour ou du rendu de la pile d'état peut entraîner des erreurs   -  person Adrian Albert Koch    schedule 14.12.2017
comment
Je voulais dire, pourquoi les gens ne peuvent-ils pas utiliser StateManager :: PushState directement ?   -  person Sebastian Redl    schedule 14.12.2017
comment
@SebastianRedl Puisque, comme je l'ai dit, cela peut ou va effectivement interrompre la boucle de mise à jour dans laquelle la pile d'état est bouclée   -  person Adrian Albert Koch    schedule 14.12.2017


Réponses (1)


Cependant, cette solution (séparer la déclaration de l'implémentation) ne fonctionne que lorsque les deux classes sont placées dans un seul fichier.

Non, cela ne fonctionne pas uniquement dans un seul fichier. Vous pouvez toujours construire un fichier identique en incluant des sous-en-têtes. Cela vous oblige simplement à faire quelque chose qui serait inhabituel avec des non-modèles (bien que la même technique fonctionne avec toutes les définitions de fonctions en ligne) : vous devez inclure un fichier après avoir défini la classe. Les en-têtes ne se limitent pas à figurer en haut du fichier malgré le nom qui leur est donné.

Donc dans un seul fichier :

  • Déclarez StateManager
  • Définir State
  • Inclure la définition de StateManager
  • Définir les fonctions membres qui dépendent de la définition de StateManager

Rien d'anormal dans l'autre fichier :

  • Inclure la définition de State
  • Définissez StateManager et ses fonctions membres.

Le résultat final est que l'inclusion de l'un ou l'autre en-tête produit les mêmes définitions et déclarations dans l'ordre requis. Ainsi, ce fractionnement des fichiers ne permet en aucun cas de limiter la quantité de recompilation provoquée par la modification d'un des en-têtes.

C'est peut-être une question de goût, mais j'inclus toujours les définitions requises par les fonctions en ligne (y compris les membres des modèles et les fonctions de modèle) après la définition de la classe. De cette façon, je n’ai pas à me soucier de savoir si cela est nécessaire.

person eerorika    schedule 14.12.2017
comment
Il a besoin de la définition complète de State avant la définition de StateManager car certaines signatures de fonction font référence à des types internes. Mais l'idée générale est bonne. - person Sebastian Redl; 14.12.2017
comment
@SebastianRedl ah, je n'ai pas remarqué que la dépendance était en discussion. Correction... Corrigé. - person eerorika; 14.12.2017
comment
@AdrianKoch State.h ne doit pas inclure StateManager.h avant que State ait été défini. Sinon, il existe une dépendance circulaire. Vous devez vous débarrasser de cette inclusion. Il n'est pas possible de dire comment, sans un exemple minimal reproductible. - person eerorika; 14.12.2017
comment
L'autre astuce que vous pouvez utiliser dans chaque en-tête enfant consiste à affirmer que l'en-tête parent a été chargé, de sorte que votre ordre a été conservé. Les en-têtes standards regorgent d'exemples comme : #if !défini _MATH_H && !défini _COMPLEX_H # erreur N'utilisez jamais ‹bits/mathdef.h› directement ; inclure ‹math.h› à la place #endif - person Gem Taylor; 14.12.2017