Partage
  • Partager sur Facebook
  • Partager sur Twitter

Classes à Sémantique de valeur/entité

Merci pour vos explications

Sujet résolu
    12 septembre 2015 à 16:51:56

    Bonjour,

    Je ne comprends pas bien la différence entre les classes « à sémantique de valeur » et « à sémantique d’entité ».

    La définition la plus aboutie que j’ai trouvée est celle du site Developpez : http://cpp.developpez.com/faq/cpp/?page=Les-classes-en-Cplusplus

    Mais pour moi, la valeur prise en exemple de 100€, c’est une valeur, pas une classe, pas un objet …

    Voici ce que je comprends. Si vous avez un peu de temps, merci de confirmer ou non ce que je comprends (Merci d’Avance !).

    Je prends un exemple : Moi aussi, j’ai essayé de mettre de la musique dans mon aquarium et de faire danser les poissons (JAVAQUARIUM : https://zestedesavoir.com/forums/sujet/447/javaquarium/).

    Pour l’instant, j’ai 3 classes : Poisson, Algue et Aquarium.

    • Je n’ai qu’un aquarium, et même si j’en achète un second il seront distinct, ou pourra pas les ajouter, … -> Classes à sémantique d’entité
    • Chaque poisson a un nom, il est donc unique … -> Classes à sémantique d’entité
    • Les Algues, c’est moins net : Leurs seules particularités, elles se reproduisent et se font manger. Mais il n’est pas possible de les considérer de façon statistique, puisque l’on va leur donner des points de vie et des conditions de reproduction (ce ne sont pas des algues microscopique, ce sont de vrais plantes) … -> Classes à sémantique d’entité

    J’ai pensé que s’il fallait fournir à chaque jour le nombre de poissons et d’algues de l’aquarium, ce pourrait être implémenté dans une classe à « sémantique de Valeur ». (Mais ce serai sûrement plus facile de faire une méthode publique de la classe Aquarium qui retourne le nombre de poissons encore vivant et une autre méthode qui retourne le nombre d’algues, pas besoin de passer par une classe !)

    En d’autre terme, à part les classes génériques de la SDL (par exemple les « complexes », les « chaines de caractères », …), qui eux sont, par définition, génériques et « à sémentique de Valeur », je ne sais pas trop comment identifier et donc créer ces classes.

    Si je suis arrivé à ces question, c’est en fait à cause de de la forme canonique des classes (http://cpp.developpez.com/cours/cppavance/). Je m’étais imposé, dans mon aquarium, pour chaque classe de respecter cette forme (constructeur par défaut, constructeur par copie, destructeur et affectation), mais en fait, ça n’a pas de sens … Je ne dupliquerai jamais mon Aquarium sur ce projet. En cherchant, j’ai trouvé que la forme canonique n’avait de sens que pour les classes « à sémantique de valeur ». (Pour moi, la forme canonique avait un sens dans le cas de l’utilisation d’allocation dynamique [new|delete] pour les membres internes, mais si on la proscrit, ça a beaucoup moins d’importance)

    Merci, là aussi si vous pouvez éclairer ma lanterne.

    • Partager sur Facebook
    • Partager sur Twitter
      12 septembre 2015 à 18:04:32

      Lu'!

      DenisDelville a écrit:

      Mais pour moi, la valeur prise en exemple de 100€, c’est une valeur, pas une classe, pas un objet …

      Tout à fait. En fait, un des problèmes des classes à sémantique de valeur, c'est qu'elle ne rend pas un service au même sens que les classes à sémantique d'entité. L'exemple typique de la classe à sémantique de valeurs, c'est les classes de valeurs mathématiques : Fractions, Réels, Naturels, etc. Faire des opérations entre ces éléments nécessitera des traitements spécifiques qu'on est bien content de pouvoir mettre dans des classes qui vont assurer cette responsabilité.

      La raison pour laquelle dans l'exercice du Javaquarium tu n'arrives pas à isoler tes éléments qui doivent être à sémantique de valeur est assez simple : ce sont toutes des classes à sémantique d'entité.

      DenisDelville a écrit:

      En cherchant, j’ai trouvé que la forme canonique n’avait de sens que pour les classes « à sémantique de valeur ».

      Plus important encore, comme généralement on a plus besoin de new/delete en C++, on a juste besoin de laisser le compilateur générer ces constructeurs comme un grand. Ou, dans le cas où  l'on a une classe à sémantique d'entité, supprimer les constructeurs de copie + opérateur d'affectation.

      • Partager sur Facebook
      • Partager sur Twitter

      Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

        12 septembre 2015 à 19:13:51

        Merci Ksass Peuk pour cette réponse rapide  :)

        Ksass`Peuk a écrit:

        ... L'exemple typique de la classe à sémantique de valeurs, c'est les classes de valeurs mathématiques : Fractions, Réels, Naturels, etc ....

        J'avais donc à peut près compris (je reformule)

        • Si l'objectif est de faire une librairie mathématique, on fait des classes à sémantique de valeur. De même si on réalise des objets générique (chaine de caractères, fonction générique, ...), on fait des classes à sémantique de valeur.
        • Par contre, si on spécialise le code pour répondre à un objectif particulier, on utilisera plutôt des classes à sémantique d'entité.

        Ksass`Peuk a écrit:

        Plus important encore, comme généralement on a plus besoin de new/delete en C++, on a juste besoin de laisser le compilateur générer ces constructeurs comme un grand. ...

         Ok Merci de m'avoir confirmer ce que je pensais,quand on utilise pas l'allocation dynamique, cette forme canonique n'est plus si importante.

        Conclusion finale: L'identification de la ségrégation entre les deux types de classes n'a pas une grande importance (car on n'utilise plus la forme canonique!)

        Conclusion de la conclusion: Un bien grand message pour cette conclusion.

        • Partager sur Facebook
        • Partager sur Twitter
          12 septembre 2015 à 20:40:48

          Salut,

          En fait,la sémantique d'une classe est faite pour t'inviter à systématiquement te poser une seule question dont la réponse aura une importance capitale par la suite :

          Puis-je,à un instant T de l'exécution de mon programme, avoir deux instances de la même classe présentant EXACTEMENT les mêmes valeurs?

          Si tu répond "oui" a cette question ( c'est le cas de classes comme Couleur, date, Position, ... ), ta classe a sémantique de valeur.

          Si tu répond "non",  ( pour ta classe Aquarium, ta classe Poisson ou i'e classe compteEnBanque) ta classe a sémantique d'entité.

          A partir de là, tu peuxdeterminer certains comportement de base: une instance d'une classe ayant sémantique de valeur pourra être copiée et être affectée à une autre instance du même type, mais ne pourra pas être utilisée dans une hiérarchie de classes ( héritage public)

          Al'inverse, tu devra tout faire pour empêcher la copie et l'affectation pour les classes ayant sementique d'entité, autrement, tu cours le risque que "quelqu'un d'autre" ne se retrouve avec le même numéro de compte en banque que toi... 

          Par contre, les classes ayant sémantique d'entité seront des candidates idéales pour servir dans une hiérarchie de classes: tu pourrais faire hériter un classe AquatiumEauDeMer de ta classe Aquarium ou une classe CompteEpargne de ta classe CompteEnBanque.

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
            12 septembre 2015 à 23:02:53

            DenisDelville a écrit:

            Mais pour moi, la valeur prise en exemple de 100€, c’est une valeur, pas une classe, pas un objet …

            Ue précision quand même. Les sémantiques d'entité et de valeur sont des sémantiques de classes. Elles définissent les propriétés intrinsèques d'une classe, ce qu'elle peut faire ou non. Ce n'est pas forcement lié aux propriétés de ce que les classes représentent concrètement, c'est une abstraction.

            Dans la "vraie vie", 100€, c'est un billet, qui a sa propre unicité, donc une entité. Mais tu peux représenter cela dans un programme par une valeur (on s'intéresse qu'à la valeur "100€") ou une entité (si tu as un billet de 100€ et que tu paies quelque chose à 30€, tu ne te retrouves pas avec un bilet de 70€).

            Dans la discussion sur ZdS sur Javaquarium, certains ont testé l'approche ECS (entity-component-system), qui modélise les composants sous forme de données (donc purement une sémantique de valeur).

            Bref, tout ça pour dire que les sémantiques d'entité et valeur sont des notions concernant les classes, pas ce qu'elles représentent. Dans certains cas, une sémantique de classe est plus naturelle en fonction de ce que la classe représente, mais c'est avant tout une question de comment on conçoit les données et la résolution des problèmes.

            DenisDelville a écrit:

            Si je suis arrivé à ces question, c’est en fait à cause de de la forme canonique des classes

            Tu as loupé un mot clé important : "fournit un cadre de base à respecter pour les classes non triviales".

            Encore une fois, on est sur une propriété transverse aux sémantiques de classe, donc ces notions doivent être considérées indépendamment (ie tu peux avoir une classe à sémantique d'entité triviale, une classe à sémantique d'entité non triviale, une classe à sémantique de valeur triviale, etc).

            Le compilateur va fournir des implémentations par défaut de ces constructeur et du destructeur. Une classe non triviale est en gros une classe pour qui les implémentations par défaut ne sont pas correctes. (Le cas le plus connu est l'utilisation des pointeurs, qui nécessite un appelle à new et à delete).

            Donc tu n'as pas à implémenter ces fonctionnalités en fonction de si tu as une sémantique d'entité ou de valeur, mais uniquement en fonction du contenu de ta classe (ses membres, le respect de l'invariant de classe, etc). Un petit bémol cependant, la sémantique d'entité n'est pas compatible avec la copie et nécessite un destructeur virtuel en général.

            D'ailleurs, pour cette problématique, il existe des règles spécifiques, indépendant de la sémantique de classe : le big-5 ("si on fournit une implémentation spécifique pour l'un des fonctionnalités, on doit implémenter les 5 - copie, mouvement, destruction"). Et le big-0 ("fiche la paie à tes classes, utilise des pointeurs intelligents et n'implémente aucune de ces fonctionnalités" :) )

            DenisDelville a écrit:

            Conclusion finale: L'identification de la ségrégation entre les deux types de classes n'a pas une grande importance (car on n'utilise plus la forme canonique!)

            Si, cela reste important. Cela ne t'impose pas l'implémentation explicite de la forme canonique (tu peux laisser le compilateur utiliser l'implémentation par défaut ou utiliser le mot-clé "default"), mais cela t'impose d'autres choses, qu'il faut respecter :

            • pas de copie dans la sémantique d'entité (donc comme le compilateur propose une implémentation par défaut de la copie, il faut la désactiver explicitement, par exemple avec le mot-clé "delete" ou boost::non_copiable)
            • destructeur virtuel dans la classe destinée à être dérivée (ou mettre la classe en final) pour la sémantique d'entité, interdit (ou plus précisément, cela n'aurait pas de sens) dans la sémantique de valeur
            • pas d'opérateur de comparaison  et d’arithmétique dans la sémantique d'entité, cela est autorisé (mais non obligatoire) dans la sémantique de valeur

            Et autre contraire, si tu souhaites utiliser certaines fonctionnalités, cela t'impose la sémantique (et donc les contraintes liées) : 

            • si tu utilises l'héritage, tu as une sémantique d'entité
            • si tu utilises des comparaison ou l'arithmétique, tu as une sémantique de valeur

            Et idem pour le respect de la forme canonique (de Coplien + extension pour la move semantic), elle est très importante à respecter. Mais généralement, le mieux est de laisser l'implémentation par défaut du compilateur.

            • Partager sur Facebook
            • Partager sur Twitter
              13 septembre 2015 à 8:39:52

              Bonjour et merci Koala,

              koala01 a écrit:

              .... Si tu répond "oui" a cette question ( c'est le cas de classes comme Couleur, date, Position, ... ), ta classe a sémantique de valeur...

              Déjà, je sentais bien qu'il y avait d'autres classes que celles des librairie mathématique pour les « classes à sémantique de valeurs », mais je n'arriver pas à trouver des exemple, j'avais pas pensé à la HMI.

              Donc, je retiens de ton message (je fais des raccourcis, si non je mémorise pas) :

              • Classe à « Sémantique de valeur » : Peut-être copier, comparer, affecter, …Mais ne pas la mettre en « héritage public »
              • Classe à « sémantique d'entité » : candidat à l'héritage de classe … Mais inhiber dans la déclaration la fonction d'affectation.

              Est ce que j'ai bien compris ton message Koala ?

              -
              Edité par DenisDelville 13 septembre 2015 à 9:36:39

              • Partager sur Facebook
              • Partager sur Twitter
                13 septembre 2015 à 9:35:59

                Bonjour et merci GBdivers,

                gbdivers a écrit:

                ... Dans la "vraie vie", 100€, c'est un billet, qui a sa propre unicité, donc une entité. Mais tu peux représenter cela dans un programme par une valeur (on s'intéresse qu'à la valeur "100€") ou une entité (si tu as un billet de 100€ et que tu paies quelque chose à 30€, tu ne te retrouves pas avec un bilet de 70€)...

                J'étais en train de répondre à Koala, quand j'ai compris que la question que j'allai lui poser, tu venais de me donné la réponse avec le billet de 100€ : Si je comprends bien (là encore je fais des raccourcies, si non je retiens rien) une même classe peut prendre les deux sémantiques, en fonction de là ou elle est utilisée. Je prends un exemple : Si je fais une HMI dans la quelle tous les messages d'erreur sont rouges, il y a des chances que je face une classe « afficheMessageDErreur ». Il est probable que cette classe à sémantique d'entité, héritera de la classe « couleur » (à sémantique de valeur), afin ce caractériser cette couleur rouge. En effet ce rouge sera le « rougeDeLErreur » (sémantique d'entité : il n'y aura qu'un seul rougeDeLErreur), et ce n'est plus l'une des couleur de l'arc en ciel

                gbdivers a écrit:

                Tu as loupé un mot clé important : "fournit un cadre de base à respecter pour les classes non triviales"...

                Le compilateur va fournir des implémentations par défaut de ces constructeur et du destructeur. Une classe non triviale est en gros une classe pour qui les implémentations par défaut ne sont pas correctes. (Le cas le plus connu est l'utilisation des pointeurs, qui nécessite un appelle à new et à delete)...

                En ce qui concerne la forme canonique, OK, vous me confortez dans mes intuition : Obligatoire si gestion dynamique de la mémoire, si non laisser le compilateur faire. (Par contre « big 5 » … Il faut que je cherche, je n'en connaît que 4 (constructeur par défaut, constructeur de copie, destructeur et copies))

                gbdivers a écrit:

                Si, cela reste important...mais cela t'impose d'autres choses, qu'il faut respecter :

                Je n'avais pas vu les chose sous cette angle : Ajouter des restriction sur l'utilisation des classes en fonction de leur sémantique, … Il faut que j'y réfléchisse : Comme vous avez plus d’expérience que moi, c'est sûrement une très bonne idée, … mais ça me surprend … (il est vrais que je n'ai jamais travailler dans des grosses équipe, pour des logicielle conséquent)

                en résumer :

                • Classe à sémantique d'entité : inhibé l'affectation, ajouté un destructeur virtuel (si peut être héritée), pas d'opérateur de comparaison et d'arithmétique.
                • Classe à sémantique de valeur : Pas de destructeur, opérateur de comparaison et d'arithmétique possible.

                J'espère avoir compris, GBDivers.  :euh:

                (Ca pour des messages c'est des messages : Moi il me faut 2 heures pour taper un texte comme comme ça ! Et encore, si j'avais quelque chose à dire sur le sujet) Merci donc pour vos réponse messieurs.


                PS: A force de parler d'héritage, est-ce que vous êtes plus riche qu'avant ?  :D

                -
                Edité par DenisDelville 13 septembre 2015 à 9:38:43

                • Partager sur Facebook
                • Partager sur Twitter
                  13 septembre 2015 à 15:08:31

                  DenisDelville a écrit:

                  là encore je fais des raccourcies, si non je retiens rien

                  Ce n'est pas un raccourcissement, c'est une reformulation selon tes propres termes et cela facilite l'acquisition. Donc ne te prive pas ;)

                  DenisDelville a écrit:

                  Il est probable que cette classe à sémantique d'entité, héritera de la classe « couleur » (à sémantique de valeur), afin ce caractériser cette couleur rouge. En effet ce rouge sera le « rougeDeLErreur » (sémantique d'entité : il n'y aura qu'un seul rougeDeLErreur), et ce n'est plus l'une des couleur de l'arc en ciel

                  Tu n'es pas loin. Sauf que "rouge" (sémantique de valeur) ne peut pas être hérité.

                  "rouge" est plus un paramètre de l'entité qu'un parent (en terme de relation hiérarchique). Tout comme "poisson rouge" et "poisson bleu" dérivent de "poisson" (classe parent de base, à sémantique d'entité) avec un paramètre "couleur".

                  Plus généralement, un classe à sémantique de valeur ne peut contenir que d'autres valeurs comme membre et ne pas avoir d'héritage (ni comme parent, ni comme enfant). Une entité peut être utilisée dans un héritage (comme parent ou enfant) et avoir des valeurs et entités comme membre.

                  Et donc oui, le "rougeDeErreur" est une entité, bien distincte de la valeur "rouge" (si tu as plusieurs valeurs "rouge", ça sera toujours le même rouge, qu'il soit utilisé dans un message d'erreur ou un poisson. Par contre, si tu as deux messages d'erreur affichés en même temps, tu verras bien 2 choses à l'écran et pas une seule chose. Même si ces messages d'erreur contiennent le même texte).

                  DenisDelville a écrit:

                  Par contre « big 5 » … Il faut que je cherche, je n'en connaît que 4 (constructeur par défaut, constructeur de copie, destructeur et copies))

                  Tu as dans la FAQ Dvp, dans la même partie que le lien que tu avais donné, une explication sur le "Big rule of three" (cf http://cpp.developpez.com/faq/cpp/?page=Les-classes-en-Cplusplus#Qu-est-ce-que-la-regle-des-grands-trois-Big-rule-of-three)

                  Le constructeur par défaut n'entre pas dans cette règle . Ne confond pas, ce n'est pas une règle qui dit les fonctions d'une classe généré automatiquement par le compilateur, cela dit que lorsque tu implémentes au moins une de ces fonctions, tu dois toutes les implémenter (ou les supprimer. Dans tous les cas, ne pas garder l'implémentation par défaut).

                  La big-5 dérive de la big-3 avec la sémantique de déplacement (constructeur et affectation). Le big-0 dit que l'on ne doit en fournir aucun si on utilise le C++ "moderne" (avec le RAII donc).

                  DenisDelville a écrit:

                  Je n'avais pas vu les chose sous cette angle : Ajouter des restriction sur l'utilisation des classes en fonction de leur sémantique, … Il faut que j'y réfléchisse : Comme vous avez plus d’expérience que moi, c'est sûrement une très bonne idée, … mais ça me surprend … (il est vrais que je n'ai jamais travailler dans des grosses équipe, pour des logicielle conséquent)

                  Les sémantiques de classes (comme beaucoup d'autres règles de bonnes pratiques ou de modèle de programmation) permettent d'avoir un cadre par défaut. Cela évite de faire des erreurs (sortir de ces règles augmente les risques de faire des erreurs - surtout sur le long termes), facilite la communication (tout le monde fait la même chose, donc tout le monde se comprend... avec de la chance), facilite le développement (en répondant à la question "qu'est ce que je dois mettre ou pas dans ma classe").

                  (HS : méfies toi de ceux qui ne respectent pas les règles parce qu'ils le trouvent mauvaises... d'expérience, c'est surtout qu'ils les comprennent mal. Il faut un très haut niveau de maîtrise dans une règle pour savoir ses limites et ne pas la respecter, tout en connaissant les conséquences de ce non respect).

                  Pour certaines choses, comme par exemple quand on dit "c'est mieux de respecter ces règles", tu dois effectivement faire confiance à ceux qui ont plus d'expériences, puisque leur effets se voient sur le long terme. Par contre, pour d'autres choses, on peut aussi te montrer le pourquoi de ces règles. Et c'est justement possible avec les interdictions de la sémantique d'entité.

                  Un code simple, avec simplement 2 classes qui affichent lorsque l'on appelle les fonctions :

                  #include <iostream>
                  #include <memory>
                  
                  class WrongEntityBase {
                  public:
                      WrongEntityBase() { std::cout << "WrongEntityBase()" << std::endl; }
                      ~WrongEntityBase() { std::cout << "~WrongEntityBase()" << std::endl; }
                      WrongEntityBase(WrongEntityBase const&) { std::cout << "WrongEntityBase(WrongEntityBase const&)" << std::endl; }
                      WrongEntityBase& operator=(WrongEntityBase const&) { std::cout << "WrongEntityBase& operator=(WrongEntityBase const&)" << std::endl; return *this; }
                      WrongEntityBase(WrongEntityBase &&) = delete;
                      WrongEntityBase& operator=(WrongEntityBase &&) = delete;
                  };
                      
                  class WrongEntity : public WrongEntityBase {
                  public:
                      WrongEntity() { std::cout << "WrongEntity()" << std::endl; }
                      ~WrongEntity() { std::cout << "~WrongEntity()" << std::endl; }
                      WrongEntity(WrongEntity const&) { std::cout << "WrongEntity(WrongEntity const&)" << std::endl; }
                      WrongEntity& operator=(WrongEntity const&) { std::cout << "WrongEntity& operator=(WrongEntity const&)" << std::endl; return *this;; }
                      WrongEntity(WrongEntity &&) = delete;
                      WrongEntity& operator=(WrongEntity &&) = delete;
                  };
                   
                  int main() {
                      {
                          // erreur : oublie du destructeur virtuel pour la classe de base
                          std::unique_ptr<WrongEntityBase> entity = std::make_unique<WrongEntity>();
                      }
                      std::cout << std::endl;
                      
                      {
                          // erreur : utilisation de la copie
                          std::unique_ptr<WrongEntityBase> entity1 = std::make_unique<WrongEntity>();
                          std::unique_ptr<WrongEntityBase> entity2 = std::make_unique<WrongEntityBase>(*entity1.get());
                      }
                      std::cout << std::endl;
                  }

                  affiche :

                  WrongEntityBase()
                  WrongEntity()
                  ~WrongEntityBase()
                  
                  WrongEntityBase()
                  WrongEntity()
                  WrongEntityBase(WrongEntityBase const&)
                  ~WrongEntityBase()
                  ~WrongEntityBase()

                  Dans les 2 cas, le destructeur de WrongEntity n'est pas appelé : l'objet n'est pas correctement détruit.

                  Dans le second, on fait une copie d'un objet WrongEntity pour créer un objet WrongEntityBase : l(objet n'est pas correctement copié.

                  On parle de type dynamique (le type d'objet réellement créé en mémoire) et de type static (le type vu par le compilateur à un moment donné dans le code... et qui peut changer selon le code)

                  DenisDelville a écrit:

                  PS: A force de parler d'héritage, est-ce que vous êtes plus riche qu'avant ?  

                  Oui, je reçois 1 euro par message posté. :D

                  -
                  Edité par gbdivers 13 septembre 2015 à 15:12:15

                  • Partager sur Facebook
                  • Partager sur Twitter
                    13 septembre 2015 à 19:47:51

                    Bonsoir,

                    Bien, ce soir je serai un peu moins bête!

                    gbdivers a écrit:

                    ... "rouge" est plus un paramètre de l'entité qu'un parent (en terme de relation hiérarchique)...

                    Tu as raison GBDivers, Je viens de regarder les classe de QT (Je pensais que nous avions une lib, et des déclarations mais non, ce sont les vrais classes). J'ai pris la classe QPen / QBrush et j'ai regardé la couleur du crayon et du pinceau: QColor. Et bien, il y a un une variable membre QColors avec un seter. QColor est inclut dans QPen, mais il n’y a pas d'héritage.

                    Je vais essayer de ne pas oublier ces règle.

                    Merci pour vos réponses Messieurs.

                    gbdivers a écrit:

                    DenisDelville a écrit:

                    PS: A force de parler d'héritage, est-ce que vous êtes plus riche qu'avant ?  

                    Oui, je reçois 1 euro par message posté. :D

                     Ca va me couter cher!  ;)



                    • Partager sur Facebook
                    • Partager sur Twitter

                    Classes à Sémantique de valeur/entité

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown