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

C++ Discussion :

Conteneur d'objets enfants


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2016
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Février 2016
    Messages : 39
    Par défaut Conteneur d'objets enfants
    Bonjour !

    J'ai besoin d'un conteneur (de préférence associatif (std::map, ...)) permettant de stocker des objets héritant tous d'une même classe tout en conservant les données des objets enfants dans le but de faire quelque chose comme ça :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     
    #include <map>
    #include <string>
     
    class Parent
    {
    public:
    	Parent();
    	void FonctionParent();
    };
     
    class Enfant : public Parent
    {
    public:
    	Enfant();
    	void FonctionEnfant();
    };
     
    class structure
    {
    public:
    	void AjouterObjet(std::string nom, Parent objet)
    	{
    		m_Objets[nom] = objet;
    	}
    	Parent RecupObjet(std::string nom)
    	{
    		return m_Objets[nom];
    	}
     
    private:
    	std::map < std::string, Parent> m_Objets;
    };
     
    int main()
    {
    	structure a;
    	Enfant enfant1, enfant2;
    	a.AjouterObjet("enfant", enfant1);
    	enfant2 = a.RecupObjet("enfant");
    	enfant2.FonctionParent();
    	enfant2.FonctionEnfant();
     
    	return 0;
    }
    Malheuresement, ce code ne fonctionne pas...
    J'ai essayer avec des template mais sans succés

    Y aurait il donc un moyen de réaliser ceci ?
    Aussi, est ce que ce genre de manipulation d'objet s'appellent le "downcasting" ?

    Merci

  2. #2
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Ça s'appelle du polymorphisme.
    Le mot clé virtual va t'aider.
    Je pense qu'un bon tuto sur les bases du C++ est nécessaire

    Au hasard, un exemple dans lequel on trouve quelques bonnes choses pour répondre à bcp de tes questions:
    Runtime polymorphism demo

  3. #3
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2016
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Février 2016
    Messages : 39
    Par défaut
    Oui c'est du polymorphisme je sais

    Mais comment est-ce que rajouter virtual à chaque méthodes de la classe mère me permettrait-il de créer un conteneur stockant plusieurs objets de différentes classes héritant de la première.

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    Le polymorphisme et le typage dynamique nécessite l'utilisation de pointeur ou référence.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Membre averti
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2016
    Messages
    39
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Gard (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Février 2016
    Messages : 39
    Par défaut
    Donc je devrai plutôt faire ça ?

    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
     
    #include <map>
    #include <string>
     
    class Parent
    {
    public:
    	Parent();
    	void FonctionParent();
    };
     
    class Enfant : public Parent
    {
    public:
    	Enfant();
    	void FonctionEnfant();
    };
     
    class structure
    {
    public:
    	void AjouterObjet(std::string nom, Parent objet)
    	{
    		m_Objets[nom] = &objet;
    	}
    	Parent RecupObjet(std::string nom)
    	{
    		return *m_Objets[nom];
    	}
     
    private:
    	std::map < std::string, Parent*> m_Objets;
    };
     
    int main()
    {
    	structure a;
    	Enfant enfant1, enfant2;
    	a.AjouterObjet("enfant", enfant1);
    	enfant2 = a.RecupObjet("enfant");
    	enfant2.FonctionParent();
    	enfant2.FonctionEnfant();
     
    	return 0;
    }

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    Il faudrait peut-être aussi changer les signatures de AjouterObjet() et RecupObjet() pour prendre des références

    Mais je pense que tu ne pourras jamais faire ce que tu veux dans ton main(). Le principe de stocker des Parents, c'est que ne sont pas des Enfants. Quand tu fais RecupObjet(), tu récupères un Parent, pas un Enfant. Ainsi, tu n'as pas accès aux méthodes définies dans la classe Enfant.

  7. #7
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Ce code est une catastrophe... Désolé
    Il faudrait lire, et sans doute aussi comprendre, les réponses qui te sont données.
    Répondre ligne par ligne aux "défauts" de ton code reviendrait à te donner un cours complet.

    En étant minimaliste:
    • Oui, tu dois rajouter virtual à ta méthode void Parent::FonctionParent();
    • Non, tu ne peux pas obtenir un conteneur stockant plusieurs objets de différentes classes héritant d'une classe de base.
      MAIS, comme le souligne Bousk, tu peux créer un conteneur stockant des pointeurs ou références vers des objets de classes différentes héritant d'une classe de base.

  8. #8
    Membre éclairé Avatar de onilink_
    Profil pro
    Inscrit en
    Juillet 2010
    Messages
    611
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2010
    Messages : 611
    Par défaut
    Citation Envoyé par martantoine Voir le message
    Aussi, est ce que ce genre de manipulation d'objet s'appellent le "downcasting" ?
    Je vais parler de cast car c'est un truc qui m'a fait un peu galérer au début de mon apprentissage.

    Si tu as:
    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
    struct Parent {};
    struct Enfant: Parent {};
     
    [...]
     
    // nos instances, que tu devra allouer dynamiquement si tu veux les mettre dans un container (dans un std::unique_ptr généralement)
    Parent parent;
    Enfant enfant;
     
    // ici on simule ton problème, en perdant l'information sur le type
    Parent* a = &parent;
    Parent* b = &enfant;
     
    // exemples de cast
    Enfant* c = static_cast<Enfant*>(b); // ok, on sait que b est un enfant, on a le droit de le cast
    Enfant* d = static_cast<Enfant*>(a); // ... pas bon, par contre aucun moyen de savoir si le retour est valide ou non au runtime, va causer des problèmes (undefined behaviours)
    Enfant* e = dynamic_cast<Enfant*>(a); // mais avec le dynamic_cast tu auras moyen de savoir si le cast a écouché, ici e va valoir nullptr.
    En bref: tu peux static_cast tes instances si tu connais leur type.
    Il existe un pattern ou tu vas utiliser une enum et un attribut dans tes instances pour indiquer leur type, afin de pouvoir static_cast.
    C'est utile si tu as peu de types différents, et utilisé seulement quand il y a besoin de hautes performances (static_cast ne coûte rien au runtime, mais t'auras le coût des conditions sur l'enum).

    dynamic_cas tva en revanche se faire au runtime (il faut le rtti d'activé du coup).
    Ce qui veut dire que tu pourras vérifier si le cast s'est bien passé, en regardant son retour, mais ça aura un coût qui peut vite devenir non négligeable selon la complexité de ton héritage et la fréquence d'utilisation.
    A connaître, mais à utiliser avec parcimonie et en connaissance de cause.

    Dans ton cas, si toutes les méthodes publiques de ton api peuvent être contenues dans ton parent, alors utilise du polymorphisme (virtual, override).
    Si tu as besoin de quelque chose de plus complexe, regarde du côté du pattern visitor. C'est du double dispatching et ça permet de faire des choses très intéressantes.


    Ps:
    Dans ton premier code j'ai l'impression que tu veux tag avec des string tes instances.
    Ne fait jamais ça. Comparer deux string est coûteux. A la limite utilise une enum, mais en C++ tu peux utiliser des templates et typeid pour faire ça automatiquement.
    Mais encore une fois, a utiliser si tu comprend bien ce que tu fais sinon tu risques d'avoir des surprises.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Le fait est que, à partir du moment où tu as été assez bête que pour perdre le type réel d'une donnée en la considérant comme étant du type de la classe de base, tu n'as pas d'autre choix que de... travailler avec cette donnée comme s'il s'agissait d'un élément du type de base.

    Le transtypage -- qu'il soit à base de static_cast ou à base de dynamic_cast -- ne devrait JAMAIS être envisagé, car il brise purement et simplement l'un des principes de base qui est l'OCP (pour Open Close Principle ou principe "ouvert fermé") qui nous dit -- pour faire simple -- qu'un code doit toujours être ouvert aux évolutions mais fermé aux modifications.

    Autrement dit: tu ne devrais jamais avoir à modifier un code existant qui a été validé (pour lequel tu as pu confirmer qu'il agissait correctement) pour pouvoir introduire une évolution au niveau de ton projet.

    Or, si tu as une classe Enfant qui hérite de Parent, tu dois t'attendre à ce que -- tôt ou tard -- tu te retrouve à devoir rajouter une classe Entant2 (et sans doute dix autres classes du même acabit) qui hérite également de la classe Parent, et du coup, à devoir modifier l'ensemble des endroits de ton code où tu as utilisé le static_cast (resp dynamic_cast) pour pouvoir introduire l'évolution.

    Le problème, c'est que tu en auras à tellement d'endroits dans ton code que tu as de très fortes chances d'en oublier un ou plusieurs de manière systématique, et que cela provoquera forcément des bugs, vu que les endroits que tu oublieras ne prendront forcément pas tous les cas dérivés possibles.

    Cependant, la bonne nouvelle, c'est que chaque donnée qui "passe pour être" du type de base au travers d'une référence ou d'un pointeur sait parfaitement de quel type réel elle est effectivement, et que, si cette donnée décide de se transmettre à elle même à une fonction, elle le fera sous a forme de son type réel.

    Il est donc possible de contourner tous ces problèmes en utilisant la technique dite du "double dispatch" dont le patron de conception visiteur est l'une des mises en oeuvre possible.

    Imaginons que nous ayons trois classes dérivées de Parent (appelons les Fille1, Fille2 et Fille3). Nous pourrions avoir un code proche de
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    class Parent{
    public:
        /* les éléments qui font partie d'une hiérarchie de classes ont 
         * "sémantique d'entité" : ils ne sont ni copiables ni assignables
         */
        Parent() = default;
        Parent(Parent const & ) = delete;
        Parent & operator = (Parent const &) = delete;
        virtual ~Parent() = default;
        /* toutes les classes fille passeront pour être du type "parent"
         * nous devons donc avoir une fonction commune au niveau
         * de cette classe
         */
        virtual void appelSpeciale() const = 0;
    };
    class Fille1 : public Parent{
    public:
     
        void appelSpeciale() const override;
        void specialFille1() const{
            std::cout<<"Fille1::specialeFille1() appelée\n";
        }
    };
    class Fille2 : public Parent{
    public:
        void appelSpeciale() const override;
        void specialFille2() const{
            std::cout<<"Fille2::specialeFille2() appelée\n";
        }
    };
    class Fille3 : public Parent{
    public:
        void appelSpeciale() const override;
        void specialFille3() const{
            std::cout<<"Fille3::specialeFille3() appelée\n";
        }
    };
    void appelFille1(Fille1 const & f){
        f.specialFille1();
    }
     
    void appelFille2(Fille2 const & f){
        f.specialFille2();
    }
    void appelFille3(Fille3 const & f){
        f.specialFille3();
    }
    void Fille1::appelSpeciale() const{
        appelFille1(*this);
    }
    void Fille2::appelSpeciale() const{
        appelFille2(*this);
    }
    void Fille3::appelSpeciale() const{
        appelFille3(*this);
    }
    int main(){
        std::vector<std::unique_ptr<Parent>> allItems;
        auto ptr1 = std::make_unique<Fille1>();
        auto ptr2 = std::make_unique<Fille2>();
        auto ptr3 = std::make_unique<Fille3>();
        allItems.push_back(std::move(ptr1);
        allItems.push_back(std::move(ptr2);
        allItems.push_back(std::move(ptr3);
        for(auto const & it : allItems){
            it.get()->appelSpeciale();
        }
        return 0;
    }
    qui donnerait un résultat proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Fille1::specialeFille1() appelée
    Fille2::specialeFille2() appelée
    Fille3::specialeFille3() appelée
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Membre éclairé Avatar de onilink_
    Profil pro
    Inscrit en
    Juillet 2010
    Messages
    611
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2010
    Messages : 611
    Par défaut
    Le double dispatch c'est la vie. Par contre il ne faut pas oublier que ça utilise deux appels virtuels, et dans certains cas ça peut devenir très coûteux (un appel virtuel n'est pas gratuit).

    Donc dire que le transtypage ne devrait jamais être envisagé, ça dépend des cas j'imagine.
    Dans une hiérarchie fermée comme un système de collision avec N shapes définies par exemple, pour le test de collision je me demande ce qui est le plus performant entre un double dispatch et un test a base d'enum.
    Personnellement j'utilise le double dispatch dans ce cas, car je me suis pas pris la tête a faire des benchmarks, mais j'ai vu beaucoup d'entreprises faire la guerre aux appels virtuels (de souvenir ubisoft en parlaient dans une cppcon).
    Si quelqu'un a des chiffres ça m’intéresse.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par onilink_ Voir le message
    Le double dispatch c'est la vie. Par contre il ne faut pas oublier que ça utilise deux appels virtuels, et dans certains cas ça peut devenir très coûteux (un appel virtuel n'est pas gratuit).
    Si tous tes visiteurs font partie d'une hiérarchie commune (ou peut s'en faut), effectivement, cela utilise deux appels virtuels.

    Mais ca, tu n'est obligé d'y avoir recours que dans le cadre d'un langage qui ne supporte que le paradigme orienté objet, lorsque tu mets en place une implémentation "classique" du patron de conception visiteur.

    Or, il se fait que C++ est multi-paradigme, et que les paradigmes impératif et générique s'intègrent parfaitement bien avec le paradigme orienté objet

    Si tu regarde attentivement le code que j'ai donné en exemple, tu remarquera que je ne fais qu'un appel virtuel, auquel je ne peux pas couper, mais que tout le reste se fait au travers de fonctions libres, qui ne pourront en aucun cas être virtuelles

    Tu as cependant tout à fait raison: un appel virtuel n'est pas gratuit, et il faut prendre ce cout en compte

    Citation Envoyé par onilink_ Voir le message
    Donc dire que le transtypage ne devrait jamais être envisagé, ça dépend des cas j'imagine.
    Il y aura toujours des cas "peau de banane", des cas extrêmes dans lesquels on pourra estimer qu'il est peut-être préférable de contrevenir au règles générales et où l'on donner le sens de "jamais sauf si" au mot jamais.

    Il faut cependant bien avoir conscience du fait que la décision de contrevenir à l'un des principes fondamentaux de la programmation (l'OCP dans le cas présent) n'est pas gratuite non plus: on paye simplement les choses de manière différente. En l'occurrence, on paye les choses sous la forme d'une dette technique: il y aura toujours un risque énorme pour que, pour ajouter une nouvelle fonctionnalité à notre programme, nous devions trouver un "pis aller" qui permette de "contourner" le problème que l'on a généré en décidant de ne pas respecter les principes fondamentaux il y a peut-être des années de cela.

    Et ce "pis aller" lui-même ne respectant pas les principes fondamentaux, il y a toujours un risque pour que nous devions -- dans six mois ou dans un an, ou plus -- trouver un autre pis aller pour arriver à contourner le problème, et ainsi de suite sur toute la durée du développement du projet.

    Et tous ces pis aller seront systématiquement des nids à emmerdes : de vrais nids à bugs, et l'origine de pertes de temps incroyables en périodes de dev.

    Tu vas donc décider de gagner quelques fréquences d'horloges en évitant un appel virtuel, mais tu risques de les payer au centuple et bien plus encore d'un autre coté.

    Citation Envoyé par onilink_ Voir le message
    Dans une hiérarchie fermée comme un système de collision avec N shapes définies par exemple, pour le test de collision je me demande ce qui est le plus performant entre un double dispatch et un test a base d'enum.
    Si tu peux garantir
    1. que ta hiérarchie de classes n'évoluera absolument jamais,
    2. que cette dérogation restera exceptionnelle
    3. que le fait d'éviter un appel virtuel aura un impact majeur sur les performances (parce que tu devrais faire plusieurs millions d'appels virtuels par secondes tout au long de l'exécution)


    Alors, oui, cela peut valoir la peine de déroger aux principes, à condition de pouvoir justifier chacune de ces conditions.

    Si tu as affaire à une hiérarchie de classes à laquelle tu rajoutera un élément tous les six mois (ou tous les ans, même), que cette dérogation devient la "manière de faire normale" à chaque fois que tu as été "assez bête" que pour perdre le type réel de ta donnée ou que cela te permet d'éviter un appel virtuel qui ne sera effectué que de manière sporadique, alors, tu te donnes à toi et à ton équipe beaucoup de mal pour rien.
    Citation Envoyé par onilink_ Voir le message
    Personnellement j'utilise le double dispatch dans ce cas, car je me suis pas pris la tête a faire des benchmarks, mais j'ai vu beaucoup d'entreprises faire la guerre aux appels virtuels (de souvenir ubisoft en parlaient dans une cppcon).
    Si quelqu'un a des chiffres ça m’intéresse.
    Les société comme UBISOFT ont carrément abandonné le paradigme orienté objet pour toute une séries d'aspects, préférant se tourner vers une approche de type ECS.

    Et oui, l'approche du "tout virtuel" n'est clairement pas la meilleure approche possible. Nous pourrions sans aucun problème faire un parallèle entre les fonctions virtuelles (et le double dispatch) et les fonctions récursives: ce sont des techniques géniales, mais qui sont adaptées à des situations bien précises, qu'il faut éviter d'utiliser à tord et à travers.

    Mais, à moins d'avoir de très bonnes raisons pour recourir au transtypage "sauvage", il est sans doute encore bien plus intéressant d'utiliser les appels virtuels, ne serait-ce que parce que cela te permet de "relier" ton code à ta conception, et que cela n'a pas de prix
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #12
    Membre éclairé Avatar de onilink_
    Profil pro
    Inscrit en
    Juillet 2010
    Messages
    611
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2010
    Messages : 611
    Par défaut
    Merci pour les précisions

    Effectivement prévoir sur le long terme est toujours la meilleure chose à faire.
    J'ai eu pas mal de problèmes de ce type à mes débuts, surtout que je travaille en solo, on fait vite des erreurs comme ça sans avoir personne pour les pointer du doigt.

Discussions similaires

  1. Réponses: 8
    Dernier message: 12/07/2008, 12h29
  2. [AS3] Conteneur d'objet
    Par superjaja dans le forum ActionScript 3
    Réponses: 1
    Dernier message: 24/04/2008, 09h46
  3. Conteneurs -> mettre objets ou pointeurs?
    Par Nykoo dans le forum Qt
    Réponses: 9
    Dernier message: 16/03/2008, 19h55
  4. Réponses: 3
    Dernier message: 15/12/2006, 18h52
  5. Afficher Objet enfants d'un tabpage
    Par dederfred dans le forum Delphi
    Réponses: 4
    Dernier message: 27/10/2006, 04h39

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