IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

[Langage] Se mettre dans une boucle d'include sans s'en sortir


Sujet :

Langage C++

  1. #1
    Membre du Club
    Inscrit en
    Mars 2004
    Messages
    84
    Détails du profil
    Informations forums :
    Inscription : Mars 2004
    Messages : 84
    Points : 42
    Points
    42
    Par défaut [Langage] Se mettre dans une boucle d'include sans s'en sortir
    Bonjour,

    Ci-dessous un exemple illustrant la difficulté que je rencontre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    #ifndef CLASS1
    #define CLASS1
     
    #include "class2.h"
     
    class class1
    {
    public:
    	class1();
    	class2 c2;
    public:
    	~class1();
    };
     
    #endif
    et la 2ème classe

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    #ifndef CLASS2
    #define CLASS2
     
    #include "class1.h"
     
    class class2
    {
    public:
    	class2();
    	class1 c1;
    public:
    	~class2();
    };
     
    #endif
    là class1 fait include à class2 et vise versa.

    Est ce qu'il y a une solution pour ce probleme sachant que malheureusement c'est une obligation dans mon programme ?

    Bien à vous

  2. #2
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Bonjour,

    Si class1 a une instance de class2 et vice versa, ce n'est pas seulement un problème d'inclusion circulaire, c'est pire que ça ! Si tu pouvais compiler ceci, en créant une instance de class1, celle-ci va créer une instance de class2, qui va elle-même créer une instance de class1 et ainsi de suite jusqu'à la destruction de l'univers (ou à un stack overflow si on a plus de chance).

    Mais à mon avis, tu voudrais plutôt que class2 ait une référence (et non une instance) de class1, n'est-ce pas ?
    Dans ce cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #ifndef CLASS2
    #define CLASS2
     
    class class1;
     
    class class2
    {
    public:
    	class2();
    	~class2();
    private:
    	class1& c1;
    };
    #endif
    @tous > Y'a pas un truc dans la FAQ, pour ça ?

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 626
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 626
    Points : 30 684
    Points
    30 684
    Par défaut
    Salut,
    Citation Envoyé par Florian Goo Voir le message
    @tous > Y'a pas un truc dans la FAQ, pour ça ?
    Si...
    Ca se trouve==>ICI<==

  4. #4
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Grmbll, j'avais cherché avec inclusion récursive/circulaire, et j'avais rien trouvé.
    Merci, ça m'aidera lors de mes prochains coups de main (pour engueuler les débutants avec plus de crédibilité )

  5. #5
    Membre averti Avatar de Rafy
    Profil pro
    Inscrit en
    Juillet 2005
    Messages
    415
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Juillet 2005
    Messages : 415
    Points : 417
    Points
    417
    Par défaut
    Sans trahir aucun secret...
    Je tiens juste à complèter ce qui a été marqué plus haut :
    On ne peut pas utiliser une référence dans ce cas la. Il faut utiliser un pointeur.

  6. #6
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Citation Envoyé par Rafy Voir le message
    Sans trahir aucun secret...
    Je tiens juste à complèter ce qui a été marqué plus haut :
    On ne peut pas utiliser une référence dans ce cas la. Il faut utiliser un pointeur.
    Pourquoi ça ?
    J'ai toujours utilisé des références dans ce type de problèmes.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 626
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 626
    Points : 30 684
    Points
    30 684
    Par défaut
    Citation Envoyé par Florian Goo Voir le message
    Pourquoi ça ?
    J'ai toujours utilisé des références dans ce type de problèmes.
    En fait, nous avons tous les deux "zappé" un détail important.

    S'il n'y avait que Class2 qui doit référencer Class1, cela poserait un problème sur lequel je reviendrai, mais, l'un dans l'autre, nous pourrions nous en sortir avec une référence (je n'avais d'ailleurs pas vu non que tu utilisais une référence, sinon j'aurais réagi vivement ).

    Mais le problème, c'est qu'une référence doit être initialisée lorsqu'elle est déclarée.

    Or, ici, cela signifie que Class1 doit disposer d'une instance existante de Class2 (pour pouvoir initialiser le membre c2), et que... Class2 doit disposer d'une instance existante de Class1 (pour pouvoir initialiser le membre c1)...

    Il nous est donc impossible de déclarer la moindre instance, car c'est le serpent qui se bouffe la queue: Class1 a besoin de Class2, qui a besoin de Class1, qui a besoin de...

    En outre, il y a le problème sur lequel j'ai juré de revenir: une référence ne peut pas être nulle.

    Or, dans l'état actuel des choses, et même s'il n'y avait que Class2 qui référençait Class1, nous n'avons aucune certitude que l'instance de Class1 référencée dans Class2 reste en permanence accessible tant que l'instance de Class2 existe.

    Pire, étant donné qu'il s'agit d'une référence, cela signifie que le destructeur de Class1 sera automatiquement appelé lorsque le destructeur de Class2 le sera (car le destructeur d'une classe finit toujours par appeler celui de ses membres non pointeurs)

    Si donc l'instance de Class1 est détruite avant celle de Class2, la référence devient invalide, et tu risque encore bien de déclencher la troisième guerre mondiale en voulant accéder à c1.

    A l'inverse, si l'instance de Class1 tient plus longtemps que l'instance de Class2, comme l'instance de Class1 aura été détruite lors de la destruction de celle de Class2, c'est au moment où l'instance de Class1 devra être détruite que tu appellera le destructeur... d'un objet qui n'existe plus... Là encore, la troisième guerre mondiale est à nos portes

    La seule alternative pour éviter cela est d'utiliser les pointeurs:

    Ils peuvent être nul, et il est donc possible d'effectuer un test dessus (à condition d'avoir pris la précaution de signaler au contenant que le contenu est détruit)

    Mais, en plus, si la variable de type "pointeur sur" est effectivement détruite, il ne s'agit en réalité que d'un size_t, car le destructeur du contenant n'ira absolument pas chipoter à... "l'adresse pointée par" la variable de type pointeur, autrement dit: à l'instance réelle de l'élément pointé, du moins, tant que tu ne dira pas explicitement de le faire.

    Et là encore, pour peu que, en cas de destruction de l'objet contenu, tu prenne la peine de mettre le pointeur à NULL, il n'y aura aucun problème: un delete sur NULL est garanti sans effet.

    Finalement, tout ce qu'il faut prévoir dans ce cas bien particulier, et bien qu'il soit peut etre opportun d'envisager le pattern observer (ou autre similaire) dans ce cas cyclique, c'est de fournir une méthode qui puisse signaler au contenant que le contenu est détruit, et qui sera appelée par le destructeur du contenu, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //dans Class1.h
    class Class2; //déclaration anticipée
    class Class1
    {
        public:
            Class1(Class2* c2);
            ~Class1();
            void unsetMember();
        private:
            Class2* c2;
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    // dans Class1.cpp
    #include "Class1.h"
    #include "Class2.h"
    Class1::Class1(Class2* c2):c2(c2)
    {
    }
    Class1::~Class1()
    {
        c2->unsetMember();
    }
    void Class1::unsetMember()
    {
        c2 = NULL;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //dans Class2.h
    class Class1; //déclaration anticipée
    class Class2
    {
        public:
            Class2(Class1* c1);
            ~Class2();
            void unsetMember();
        private:
            Class1* c1;
    };
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    // dans Class2.cpp
    #include "Class1.h"
    #include "Class2.h"
    Class2::Class2(Class1* c1):c1(c1)
    {
    }
    Class2::~Class2()
    {
        c1->unsetMember();
    }
     
    void Class2::unsetMember()
    {
        c1 = NULL;
    }
    Et le tour est joué (à l'extrême limite, nous pourrions même envisager de déclarer une amitié de la classe contenue avec la fonction unsetMember contenante, et de déclarer cette fonction privée, histoire d'éviter la tentation aux gens d'aller l'appeler par eux même)

  8. #8
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    C'est vrai que ton explication se tient. Mais j'ai malgré tout testé moi-même, et ceci compile avec GCC (avec -W -Wall -pedantic, qui plus est) :

    class1.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    #ifndef CLASS1
    #define CLASS1
     
    #include "class2.h"
     
    class class1
    {
    public:
    	class1();
    	~class1();
    private:
    	class2 c2;
    };
     
    #endif
    class1.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    #include "class1.h"
     
    #include <iostream>
     
    class1::class1():
    	c2(*this) //mais c'est vrai que ceci me choque...
    {
    	std::cout << "class1()" << std::endl;
    }
     
    class1::~class1()
    {
    	std::cout << "~class1()" << std::endl;
    }
    class2.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    #ifndef CLASS2
    #define CLASS2
     
    class class1;
     
    class class2
    {
    public:
    	class2(class1& c);
    	~class2();
    private:
    	class1& c1;
    };
     
    #endif
    class2.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    #include "class2.h"
     
    #include <iostream>
     
    class2::class2(class1& c):
    	c1(c)
    {
    	std::cout << "class2()" << std::endl;
    }
     
    class2::~class2()
    {
    	std::cout << "~class2()" << std::endl;
    }
    main.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    #include <iostream>
    #include "class1.h"
     
    int main()
    {
    	{
    		class1 c;
    	}
     
    	std::cout << "I'm still alive!" << std::endl;
     
    	return 0;
    }
    … et s'exécute (remarque : contrairement aux apparences, c'est bien class1 qui est créé en premier et détruit en dernier) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    class2()
    class1()
    ~class1()
    ~class2()
    I'm still alive!
    Ceci dit, si on essaie d'accéder à une référence dont l'objet est détruit, on aura le droit à une erreur de segmentation, ça, c'est certain.

  9. #9
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par Florian Goo Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class1::class1():
    	c2(*this) //mais c'est vrai que ceci me choque...
    Si gcc n'est pas bavard, visual te prévient:
    Citation Envoyé par Visual
    warning C4355: 'this' : used in base member initializer list
    D'ailleurs, ça se comprend. Voilà un bel exemple qui plante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class class2
    {
    public:
    	class2(class1& c);
    	~class2()
       {
    	   std::cout << "~class2()" << std::endl;
       }
    private:
    	class1& c1;
    };
    et:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class class1
    {
    public:
    	class1():c2(*this),m_str("toto")
       {
          std::cout << "class1()" << std::endl;
       }
    	~class1()
       {
    	   std::cout << "~class1()" << std::endl;
       }
     
    private:
    	class2 c2;
    public:
       std::string m_str;
    };
    enfin:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class2::class2(class1& c):c1(c)
    {
       std::cout << "class2()" << std::endl;
       std::cout<<c1.m_str<<std::endl;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main()
    {
    	{
    		class1 c;
    	}
     
    	std::cout << "I'm still alive!" << std::endl;
     
       return std::cin.get();
    }
    Normal! Le premier objet n'est pas complètement construit que class2 peut déjà l'utiliser!

  10. #10
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Normal! Le premier objet n'est pas complètement construit que class2 peut déjà l'utiliser!
    Oui, c'est pour ça que ça me choquait
    J'ai été surpris que GCC ne me sorte pas au moins un warning (malgré le niveau de warning maximum).

    Enfin, tout étant dit, je cesse d'insister. Il est vrai qu'utiliser une référence est ici dangereux, même si c'est faisable

  11. #11
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 626
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 626
    Points : 30 684
    Points
    30 684
    Par défaut
    Mais là, tu te places dans le cas où l'existence d'une instance de class2 dépend exclusivement d'une instance de class1, et, surtout, tu ne n'essaye pas d'utiliser class2.

    Mais, lorsque tu va remplir tes deux classes, si tu as pris la peine de créer cette dépendance circulaire, c'est sans doute et avant tout parce que, dans ta tête, tu as prévu de pouvoir appeler des méthodes d'une des deux classes dans au moins une méthode de l'autre, et que, de manière générale, tu as prévu de pouvoir créer une instance séparée des deux classes (déclarer class2 var(c), ou class2& var = c.getC2(); puis appeler une méthode de var qui risque d'appeler une méthode de c )

    Tôt ou tard, tu en viendra donc à tenter d'accéder à un objet qui aura été détruit

  12. #12
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 379
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 379
    Points : 41 573
    Points
    41 573
    Par défaut
    Cet exemple de plantage arrive aussi bien avec une référence qu'avec un pointeur...

  13. #13
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Oui, mais la différence c'est qu'avec une référence, on est obligé de faire cela (le pointeur peut être initialisé plus tard).

Discussions similaires

  1. Programme a mettre dans une boucle
    Par oliviernouhi dans le forum Langage
    Réponses: 30
    Dernier message: 22/07/2011, 12h13
  2. comment mettre un tableau dans une boucle et sur la meme page
    Par pikkolina dans le forum Général JavaScript
    Réponses: 12
    Dernier message: 24/05/2009, 19h21
  3. [Template] Comment affecter une variable dans un include dans une boucle
    Par Daxou31 dans le forum Bibliothèques et frameworks
    Réponses: 1
    Dernier message: 05/10/2008, 15h03
  4. Mettre une tempo dans une boucle
    Par lilyla dans le forum MATLAB
    Réponses: 2
    Dernier message: 12/02/2007, 17h04
  5. [langage] incrementation de variable dans une boucle
    Par mimilou dans le forum Langage
    Réponses: 15
    Dernier message: 16/04/2004, 13h23

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo