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 :

Les préconditions en C++ [Tutoriel]


Sujet :

C++

  1. #1
    Expert éminent sénior

    Avatar de Francis Walter
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2012
    Messages
    2 315
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Bénin

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

    Informations forums :
    Inscription : Février 2012
    Messages : 2 315
    Points : 26 889
    Points
    26 889
    Par défaut Les préconditions en C++
    Bonjour,

    Je vous présente un tutoriel sur les préconditions en C++ de Andrzej Krzemieński : https://akrzemi1.developpez.com/tuto...remiere-partie
    Il s'agit de la première partie d'une série de quatre épisodes que nous allons faire l'effort de traduire et publier dans les jours à venir. Cette première partie a été traduite par kurtcpp que l'équipe de la rédaction tient à remercier sans oublier les autres contributeurs qui ont aidé à la correction.

    N'hésitez pas à commenter le contenu de ce premier article en répondant à cette discussion.

    P.-S. Si vous désirez contribuer aux traductions de ressources C++, vous pouvez me contacter par MP.



    Les meilleurs cours et tutoriels pour apprendre la programmation C++

  2. #2
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Excellente idée que cette traduction. Inspiré par un autre article sur le sujet, j'avais rédigé mes propres macros pour la validation de prédicats par le compilateur dans un simple fichier .hpp et c'est plutot efficace.

    contract.hpp

    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
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    #ifndef __CONTRACT_HPP
    #define __CONTRACT_HPP
     
    #include <Uncopyable.hpp>
    #include <type_traits>
    #include <cassert>
     
    /*!
     * \macro   require
     * \brief   Vérifie la validité d'une précondition.
     */
    #if defined(CONTRACT_NO_PRECONDITION) || defined(CONTRACT_NO_CHECK)
        #define require(contract, text)
    #else
        #define require(contract, text) assert(contract && text)
    #endif
     
    /*!
     * \macro   ensure
     * \brief   Vérifie la validité d'une postcondition.
     */
    #if defined(CONTRACT_NO_POSTCONDITION) || defined(CONTRACT_NO_CHECK)
        #define ensure(contract, text)
    #else
        #define ensure(contract, text) assert(contract && text)
    #endif
     
    /*!
     * \macro   invariant
     * \brief   Vérifie la validité d'un invariant.
     */
    #if defined(CONTRACT_NO_INVARIANT) || defined(CONTRACT_NO_CHECK)
        #define invariant(contract, text)
    #else
        #define invariant(contract, text) assert(contract && text)
    #endif
     
    /*!
     * \macro   invariants
     * \brief   Débute un bloc d'invariants de classe.
     */
    #define invariants(classname) friend class InvariantsChecker<classname>; void _contract_check_invariants() const
     
    /*!
     * \macro   check_invariants
     * \brief   Vérifie la validité des invariants de classe.
     */
    #if defined(CONTRACT_NO_INVARIANT) || defined(CONTRACT_NO_CHECK)
        #define check_invariants()
    #else
        #define check_invariants() _contract_check_invariants()
    #endif
     
    /*!
     * \macro   static_invariants
     * \brief   Débute un bloc d'invariants statiques de classe.
     */
    #define static_invariants(classname) static void _contract_check_static_invariants() const
     
    /*!
     * \macro   check_static_invariants
     * \brief   Vérifie la validité des invariants statiques de classe.
     */
    #if defined(CONTRACT_NO_INVARIANT) || defined(CONTRACT_NO_CHECK)
        #define check_static_invariants()
    #else
        #define check_static_invariants() _contract_check_static_invariants()
    #endif
     
    /*!
     * \class   InvariantsChecker
     * \brief   Vérifie la validité des invariants en début et en fin de scope.
     */
    template <typename T>
    class InvariantsChecker : private Uncopyable
    {
        private:
     
            T *instance;
     
        public:
     
            InvariantsChecker(T *instance) :
                instance(instance)
            {
    #if !defined(CONTRACT_NO_INVARIANT) && !defined(CONTRACT_NO_CHECK)
                instance->_contract_check_invariants();
    #endif
            }
     
            ~InvariantsChecker()
            {
    #if !defined(CONTRACT_NO_INVARIANT) && !defined(CONTRACT_NO_CHECK)
                instance->_contract_check_invariants();
    #endif
            }
    };
     
    #endif // __CONTRACT_HPP
    On peut alors facilement l'utiliser :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int divide(int dividend, int divisor)
    {
        require(divisor != 0, "Division par zéro indéfinie.");
        return dividend / divisor;
    }
    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
    class Square
    {
        private:
     
            size_t width;
            size_t height;
     
            invariants(Square)
            {
                invariant(width == height, "Un carré a 4 côtés égaux.");
            }
     
        public:
     
            Square(size_t width, size_t height) : 
                width(width), height(height)
            {
                check_invariants();
            }
            void resize(size_t width, size_t height)
            {
                InvariantsChecker<Square> check(this);
                this->width = width;
                this->height = height;
            }
            size_t getWidth() const
            {
                InvariantsChecker<Square> check(this);
                return width;
            }
            size_t getHeight() const
            {
                InvariantsChecker<Square> check(this);
                return height;
            }
    };

  3. #3
    Expert éminent sénior

    Avatar de Francis Walter
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2012
    Messages
    2 315
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Bénin

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

    Informations forums :
    Inscription : Février 2012
    Messages : 2 315
    Points : 26 889
    Points
    26 889
    Par défaut Préconditions en C++ - Partie 2
    Bonjour,

    Je vous annonce la deuxième partie de la série de tutoriels sur les préconditions en C++ écrits par Andrzej Krzemieński. Elle a été traduite par kurtcpp.

    Lire le tutoriel : Préconditions en C++ - Partie 2

    Citation Envoyé par Auteur
    Dans ce billet, je continuerai à partager mes réflexions sur les préconditions. Il traitera un peu de la philosophie qui est derrière le concept des préconditions (et des bugs), et étudiera la possibilité de mettre à profit le compilateur pour vérifier certaines préconditions. Plusieurs lecteurs ont fourni des retours utiles dans mon précédent billet, j'essaierai de les incorporer à celui-ci.
    Autres ressources du même auteur : Blog de Andrzej Krzemieński.

  4. #4
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    En réutilisant les macros précédentes, faciles à mettre en oeuvre, on peut réaliser des "classes contractuelles" permettant d'imposer un contrat-type à une série de classes filles mais aussi de gérer les cas problématiques d'appels récursifs ou de retours multiples. Exemple :

    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
    class ContractualClass
    {
        private:
     
            virtual int doFunc(int n) // cette fonction est un exemple de retour multiple sans contrat
            {
                if (n > 0)
                {
                    return 1/n;
                }
                else
                {
                    return 2/n;
                }
            }
     
        public:
     
            int func(int n)
            {
                require(n != 0, "n différent de 0.");
                int ret = doFunc(n);
                ensure(ret >= -2 && ret <= 1, "Valeur de retour dans l'intervalle [-2, 1].");
                return ret;
            }
    };
     
    class InheritedContractClass : public ContractualClass
    {
        private:
     
            virtual int doFunc(int n)
            {
                return 10*n; // violation du contrat de la classe mère !
            }
    };
    On peut même imaginer redéfinir un nouveau contrat qui entre dans le domaine du contrat hérité :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class InheritedContractClass : public ContractualClass
    {
        public:
     
            int func(int n)
            {
                int ret = ContractualClass::func(n);
                ensure(ret >= -2 && ret <= 0, "Valeur de retour dans l'intervalle [-2, 0].");
                return ret;
            }
    };
    Ici la valeur de retour ne doit plus être comprise dans l'interval [-2, 1] mais dans un nouvel intervalle [-2, 0].

  5. #5
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    Cela s'appelle le pattern NVI, pour Non-Virtual-Interface.
    Par contre, cela se corse en cas d'héritage de multiple "interface/contrats" pour traiter les invariants.

    -> http://luchermitte.github.io/blog/20...e-c-plus-plus/

  6. #6
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Par contre, cela se corse en cas d'héritage de multiple "interface/contrats" pour traiter les invariants.
    Avec l'approche que je donne, chaque implémentation (doFunc()) possède une fonction d'appel public (func()) vérifiant les invariants. Il n'y a donc aucune confusion sur le contrat. En revanche, un développeur imprudent pourrait alléger le contrat en redéfinissant la fonction d'appel public de façon erronée mais ça entre aussi dans le cadre du changement de portée permis par l'héritage C++ par exemple. En principe on ne le fait pas, à moins d'avoir une très bonne raison.

  7. #7
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    Ta vérification d'invariant n'est pas factorisée : à chaque fonction, tu dois vérifier les invariants courants et enfants, plus les préconditions, et à la sortie: les invariants (courant et enfants) plus les post-conditions. Ca c'est sur un seul contrat.

    Arrive le second contrat, l'invariant porte maintenant sur les deux contrats et sur la classe concrète. Cela veut dire que les fonctions du contrat 1 doivent vérifier les contrats venant du contrat 2 et de la classe concrète qu'il ne connait pas.

    Ce n'est pas si simple quand on doit tout faire à la main. Ce qui me fait penser que j'avais été déçu par P0287R0, il ne respecte pas le LSP, voire cela fait tout le contraire. J'espère qu'ils corrigeront le tir.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    // OK, we can not relax preconditions as we have already seen
    double g(double);
    double (*pg)(double x) [[expects: x != 0]] = &g; // ERROR.
     
     // but we can strengthen them ...
    double f(double x) [[expects: x >= 0]];
    double (*pf)(double) = &f; // OK.

  8. #8
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Oui effectivement, c'est ce que j'ai en tête en appellant la fonction de la classe mère lors de sa redéfinition. Il est nécessaire de le faire manuellement mais ça n'est pas une limitation uniquement liée à la programmation par contrat. Il est possible de redéfinir une fonction dans une classe fille sans appeler la fonction de la classe mère, ce qui est assez casse-gueule.

    Si je prend un exemple bidon de 2 héritages :

    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
    class ContractualClass1
    {
        private:
     
            virtual int doFunc(int n) = 0;
     
        public:
     
            int func(int n)
            {
                require(n != 0, "n différent de 0");
                int ret = doFunc(n);
                ensure(ret >= -2 && ret <= 1, "Valeur de retour dans l'intervalle [-2, 1].");
                return ret;
            }
    };
     
    class ContractualClass2 : public ContractualClass1
    {
        private:
     
            virtual int doFunc(int n)
            {
                if (n > 0)
                {
                    return 5/n;
                }
                else
                {
                    return 6/n;
                }
            }
    };
     
    class ContractualClass3 : public ContractualClass2
    {
        public:
     
            int func(int n)
            {
                int ret = ContractualClass2::func(n);
                ensure(ret >= -2 && ret <= -1, "Valeur de retour dans l'intervalle [-2, -1].");
                return ret;
            }
    };
     
    int main()
    {
        ContractualClass2 instance2;
        instance2.func(-7);
     
        ContractualClass3 instance3;
        instance3.func(-7);
     
        return 0;
    }
    Le contrat de la classe 1 reste vérifié.

  9. #9
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    Tes contrats ne sont pas orthogonaux.
    As-tu regardé l'exemple que je donne dans mon lien ?

  10. #10
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Tes contrats ne sont pas orthogonaux.
    A mon sens ça n'a pas lieu d'être. L'héritage multiple est utile pour l'implémentation d'interfaces et les interfaces étant dépourvues de données membres, elles sont dépourvues d'invariants.
    De plus, la seule chose qu'on peut espérer d'un contrat, c'est qu'il soit appliqué tel quel ou qu'il soit durci. Un mix de contrats, possiblement antagonistes, n'a pas de sens, ne correspond à aucune logique.

    As-tu regardé l'exemple que je donne dans mon lien ?
    Ton blog où le ton post sur reddit?

  11. #11
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    Tout l'intérêt des interfaces et d'isoler les responsabilités pour les avoir les plus atomiques possibles -> c'est l'ISP.
    Les objets concret peuvent alors implémenter plusieurs interfaces en toute quiétude dans la mesure où elles seront orthogonales. C'est le cas d'héritage multiple qui marche sans trop de soucis. C'est bien bien pour ça qu'il est celui gardé en Java.
    Je ne dis pas que chaque utilisation d'héritage multiple est pertinente, etc. Juste que parmi les pertinentes, on a justement des interfaces orthogonales, et que là où ça devient intéressant, c'est quand ces interfaces sont en fait des contrats.

    Pour le lien, je parlai du billet de blog, exemple avec l'héritage multiple de contrats.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Salut,
    Citation Envoyé par RPGamer Voir le message
    A mon sens ça n'a pas lieu d'être. L'héritage multiple est utile pour l'implémentation d'interfaces et les interfaces étant dépourvues de données membres, elles sont dépourvues d'invariants.
    Ca, c'est une vision très... java de ce que sont les interfaces! Elle est imposée en java pour éviter les problèmes liés à l'héritage en losange (ou en diamant, comme tu veux) pour l'unique raison que toute classe hérite forcément -- de manière directe ou indirecte -- d'une classe Object.

    Mais, si tu y porte un tout petit peu d'attention, tu te rendras compte que la notion d'interface en java (et le mot clé implements qui y est rattaché) réagit exactement de la même manière que la notion de classe ou de structure dans un contexte d'héritage, en respectant scrupuleusement le LSP.

    Dés lors, si l'on remet les pendules à l'heure pour ce qui concerne la notion d'interface, et que l'on considère que c'est "un ensemble regroupant un certain nombre de comportement destinés à travailler correctement de concert dans un but clairement déterminé" (et tu avoueras que cette définition correspond sérieusement à la notion d'interface ) on se rend compte que c'est exactement la même chose qu'une classe, surtout en C++, vu qu'il ne dispose pas de la possibilité de faire la distinction.

    Dés lors, prenons l'exemple une interface IPositionnable. En java, tu devrais avoir quelque chose ressemblant à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface IPositionnable{
        public void moveTo(Position newPos);
        public void move(Type diffX, Type diffY /*, Type diffZ*/);
    };
    public class MaClasse implements IPositionnable{
    /* il faut définir les comportements hérités de IPositionnable dans toutes les classes
     * implémentant cette interface
     */
    };
    Mais, il n'y a rien qui nous empêche en C++ d'avoir quelque chose ressemblant à
    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
    class IPositionnable{
    public:
        void moveTo(Position const & newPos){
            pos_=newPos;
        }
        void move(Type diffX, Type diffY /*, Type diffZ*/){
            pos_ = Position(pos_.x()+diffX, pos_.y()+diffY/*,pos.z()+diffZ*/);
        }
        Type x() const{
            return pos_.x();
        }
        Type y() const{
            return pos_.y();
        }
        /*
        Type z() const{
            return pos_.z()
        }
        */
    private:
        Position pos_;
    };
    class MaClass : public IPositionnable{
    /* on n'a même plus besoin de faire quoi que ce soit pour supporter l'interface IPositionnable
     */
    };
    voire, pourquoi pas, mieux encore :
    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
    template <typename CRTP, typename V, typename F>
    class IPositionnable{
    public:
        using value_t = V;
        using flag_t = F;
        using pos_t = Position<value_t, flag_t>;
        void moveTo(pos_t const & newPos){
            pos_ = newPos;
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag2D>::value>::type>
        void move(value_t diffX, value_t diffY){
            pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY};
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
        void move(value_t diffX, value_t diffY, value_t diffZ){
            pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY, pos_.z()+diffZ};
        }
     
        value_t x() const{
            return pos_.x();
        }
        value_t y() const{
            return pos_.y();
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
        value_t z() const{
            return pos_.z()
        }
    private:
        pos_t pos_;
    };
    class MaClasse : public IPositionnable<MaClasse, double, Flag3D>{
        /* il n'y a plus rien à faire en ce qui concerne l'interface IPositionnable */
    };
    (la version template de IPositionnable est faite à main levée, il y a peut être quelques corrections à apporter )

    la classe IPositionnable correspond bel et bien à la notion d'interface (c'est une classe qui regroupe un ensemble de comportements "destinés à travailler ensemble"), et pourtant, elle dispose de "tout ce qu'il faut" pour pouvoir définir les comportements en question.

    Mieux encore, la version template de cette classe permet, grâce au CRTP, d'éviter que l'interface serve de "glue artificielle" pour regrouper deux hiérarchies de classes entre elles sous prétexte "qu'elles sont toutes les deux positionnables".

    En deux mots comme en cent, la restriction imposée par java en ce qui concerne la notion d'interface est spécifique à java et n'est due qu'au fait que ce langage a effectivement forcé la distinction "physique" entre les notions de classes et d'interfaces, alors que conceptuellement parlant, il n'y a absolument rien que pourrait faire une classe qu'une interface ne pourrait faire (à moins que ce soit le contraire )

    Mais, du coup, on se rend donc aussi compte que les invariants destinés à garantir le bon fonctionnement de l'interface se trouvent... au niveau de l'interface, et non de la classe qui l'implémente

  13. #13
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    A mon sens il n'y a pas de vision "Java" de l'interface. Java a simplement une philo plus assistante. L'interface "impose" l'implémentation d'une interface, c-à-d d'une série de fonctions membres ou méthodes ici (implicitement publics) qui forment tout ou partie du service de la classe implémentante. Par exemple une interface "Positionnable" imposerait à toute classe l'implémentant de définir des fonctions membres permettant à toutes ses instances d'être positionnées (dans un système polaire ou cartésien par exemple). A partir de là, une interface avec des données membres, à fortiori des données membres privées qui plus est, n'a a mon avis pas grand sens. En tout cas ça n'est plus interface, c'est un mix entre une interface et une classe, une sorte de classe abstraite?

    Pour les contrats orthogonaux, j'ai bien compris le problème mais mis à par se casser les dents, je ne vois pas l'intérêt de prendre le risque d'avoir des contrats non compatibles ou en tout cas dont le mélange donnerait quelque chose de farfelu à respecter pour l'utilisateur.

  14. #14
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 282
    Points : 11 036
    Points
    11 036
    Par défaut
    NB: j'ai répondu trop vite tout à l'heure.
    Effectivement, une interface pure à la Java n'a pas exactement d'état. Est-ce pour autant qu'elle ne peut pas avoir d'invariant ? Je n'en suis pas sûr. On peut toujours exprimer l'invariant avec les informations publiques mises à disposition par la classe. p.ex:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    [[inv: capacity() <= size()]] struct IBufferedContainer;
    [[inv: is_sorted(begin(), end())]] struct ISortedContainer;
     
    struct sorted vector : IBufferedContainer, ISortedContainer;

  15. #15
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Peut-être. Conceptuellement, la notion de contrat peut éventuellement être étendue à tous composants (données, fonction, sous-classe, etc.) et toutes opérations (appel, instanciation, héritage, composition, etc.). C'est une question de possibilités offertes par le langage (et d'intérêt pratique surtout). En l'occurence C++ ne le peut pas encore et avec une interface adieux NVI.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par RPGamer Voir le message
    A mon sens il n'y a pas de vision "Java" de l'interface. Java a simplement une philo plus assistante. L'interface "impose" l'implémentation d'une interface, c-à-d d'une série de fonctions membres ou méthodes ici (implicitement publics) qui forment tout ou partie du service de la classe implémentante. Par exemple une interface "Positionnable" imposerait à toute classe l'implémentant de définir des fonctions membres permettant à toutes ses instances d'être positionnées (dans un système polaire ou cartésien par exemple).
    Non, la seule chose qu'impose la notion d'interface (et que j'ai par malheur oublié de citer dans mon intervention précédente) est effectivement d'être abstraite.

    Mais il y a d'autres moyen d'assurer cette restriction que l'utilisation de fonction virtuelles pures à la manière java! : si tu places le destructeur de l'interface (et tant qu'à faire, le constructeur, juste pour respecter le principe du miroir), dans l'accessibilité protégée, tu obtiens bel et bien une classe qui n'est pas instanciable en tant que telle, mais qui l'est au travers des classes qui en dérivent.

    Ainsi, si on modifie un tout petit peu les codes que j'ai présentés plus haut pour leur donner la forme 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
    class IPositionnable{
    public:
        void moveTo(Position const & newPos){
            pos_=newPos;
        }
        void move(Type diffX, Type diffY /*, Type diffZ*/){
            pos_ = Position(pos_.x()+diffX, pos_.y()+diffY/*,pos.z()+diffZ*/);
        }
        Type x() const{
            return pos_.x();
        }
        Type y() const{
            return pos_.y();
        }
        /*
        Type z() const{
            return pos_.z()
        }
        */
    protected:
        /* il n'y a de toutes façons pas de sens à permettre la destruction d'une instance
         * dérivée alors qu'on la connait comme étant du type de l'interface, 
         * un destructeur protégé et non virtuel fait donc pleinement l'affaire
         */
        ~IPositionnable() = default;
       /* et tant qu'à faire, pour respecter le principe du miroir */
       IPositionnable(Position const & p=Position{}):pos_(p){
       }
    private:
        Position pos_;
    };
    ou sous la forme template 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
    template <typename CRTP, typename V, typename F>
    class IPositionnable{
    public:
        using value_t = V;
        using flag_t = F;
        using pos_t = Position<value_t, flag_t>;
        void moveTo(pos_t const & newPos){
            pos_ = newPos;
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag2D>::value>::type>
        void move(value_t diffX, value_t diffY){
            pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY};
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
        void move(value_t diffX, value_t diffY, value_t diffZ){
            pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY, pos_.z()+diffZ};
        }
     
        value_t x() const{
            return pos_.x();
        }
        value_t y() const{
            return pos_.y();
        }
        template <typename U = F,
                   typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
        value_t z() const{
            return pos_.z()
        }
     
    protected:
        /* il n'y a de toutes façons pas de sens à permettre la destruction d'une instance
         * dérivée alors qu'on la connait comme étant du type de l'interface, 
         * un destructeur protégé et non virtuel fait donc pleinement l'affaire
         */
        ~IPositionnable() = default;
       /* et tant qu'à faire, pour respecter le principe du miroir */
       IPositionnable(pos_t const & p=pos_t{}):pos_(p){
       }
    private:
        pos_t pos_;
    };
    tu obtiens bel et bien quelque chose qui ne peut être instancié qu'au travers des classes qui en dérivent, et le fait que les comportements exposés soient effectivement définis au niveau de la classe de base n'en font pas moins des classes présentant toutes les notions indispensables à la notion d'interface

    Après, que tu ailles plus loin pour pouvoir mettre en place des politiques de positionnement en système polaire ou cartésien ne correspond qu'à un "détail d'implémentation" (qui pourrait, le cas échéant, être lui aussi défini à l'aide d'un flag particulier dans la version template)
    A partir de là, une interface avec des données membres, à fortiori des données membres privées qui plus est, n'a a mon avis pas grand sens. En tout cas ça n'est plus interface, c'est un mix entre une interface et une classe, une sorte de classe abstraite?
    uniquement parce que la vision que tu as d'une interface est biaisée par les restrictions imposées par un langage (qui, pour notre malheur, est sans doute le langage qui a réellement donné naissance à la notion d'interface).

    Mais à partir du moment où tu considère la notion d'interface comme une notion de conception pure, les règles imposées par un langage particulier n'ont forcément plus cours, vu qu'une notion conceptuelle est forcément indépendante de tout langage.

    Et quand tu t'affranchis des règles imposées par java, tu te rend compte que la seule qualité imposée par une interface est de ne pouvoir être instanciée qu'au travers d'une classe qui implémente l'interface en question.

    Bien sur, les fonctions virtuelles pures sont l'une des solutions dont on dispose pour y arriver, mais je viens de démontrer qu'il en existe d'autres et, à partir de ce moment là, on n'en a plus rien à foutre si l'interface dispose des détails d'implémentations permettant de définir précisément les comportements auxquels nous sommes en droit de nous attendre de la part de l'interface.

  17. #17
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    C'est dommage qu'on s'éloigne du sujet de base mais je fais pleinement la différence entre une classe abstraite et une interface (qui est aussi abstraite par la force des choses). A mon avis ls interfaces ne sont pas plus Javaesques que la PpC est Eiffeilesque (petit retour au sujet )

    En ajoutant une implémentation dans ton interface, tu prives l'utilisateur de définir sa propre implémentation car la force de l'interface est justement d'imposer l'interface et non l'implémentation. Un exemple comme celui-ci me paraît bon à ce sujet :

    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
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    // interface "flottant"
    struct Floatable
    {
        virtual void moveForward(...) = 0;
        virtual void moveBackward(...) = 0;
        virtual void turn(...) = 0;
    };
     
    // interface "volant"
    struct Flyable
    {
        virtual void takeOff(...) = 0;
        virtual void land(...) = 0;
        virtual void fly(...) = 0;
    };
     
    // interface "roulant"
    struct Rollable
    {
        virtual void accelerate(...) = 0;
        virtual void brake(...) = 0;
        virtual void turn(...)  = 0;
        virtual void honk() = 0;
    };
     
    // classe abstraite "véhicule"
    class Vehicle
    {
        private:
     
            unsigned int fuelLevel;
     
        protected:
     
             Vehicle() : fuelLevel(0) {}
             virtual ~Vehicle() = default;
     
        public:
     
            virtual void fillUp(unsigned int fuel)
            {
                fuelLevel += fuel;
            }
    };
     
    // avion à réaction
    class JetPlane : public Vehicle, private Flyable
    {
        private:
     
            unsigned int numPassengers;
            ...
     
        public:
     
            JetPlane() : Vehicle(), numPassengers(0) {}
     
            virtual void takeOff(...)
            {
                // décollage horizontal
            }
     
            virtual void land(...)
            {
                // attérrissage horizontal
            }
     
            virtual void fly(...)
            {
                // vol en haute altitude
            }
     
            void boarding()
            {
                // embarquement des passagers
            }
    };
     
    // hélicoptère
    class Helicopter : public Vehicle, private Flyable
    {
        public:
     
            Helicopter() : Vehicle() {}
     
            virtual void takeOff(...)
            {
                // décollage vertical
            }
     
            virtual void land(...)
            {
                // attérissage vertical
            }
     
            virtual void fly(...)
            {
                // vol en basse altitude
            }
    };
     
    // bâteau cargo
    template <typename C>
    class CargoBoat : public Vehicle, private Floatable
    {
        private:
     
             C cargo; // notre cargaison
     
        public:
     
           CargoBoat() : Vehicle() {}
     
           virtual void moveForward(...)
           {
               // on fait avancer le cargo
           }
     
           virtual void moveBackward(...)
           {
               // on fait une marche arrière, très utile pour l'entrée en port
           }
     
           virtual void turn(...)
           {
               // on tourne à droite ou à gauche
           }
     
           void load(C cargo)
           {
               // on charge la cargaison
           }
    };
     
    // voiture de sport
    class SportCar : public Vehicle, private Rollable
    {
        private:
     
            Color color;
            Brand brand;
     
        public:
     
             SportCar(Color color, Brand brand) : 
                Vehicle(),
                color(color),
                brand(brand)
             {}
     
             virtual void accelerate(...) 
             {
                 // on fait une petite acceleration
             }
     
             virtual void brake(...)
             {
                 // on freine, il faut bien!
             }
     
             virtual void turn(...)
             {
                 // on tourne à droite ou à gauche
             }
     
             virtual void honk()
             {
                 // biiip !
             }
    };
    On va ainsi être capable de définir définir ce qu'est un véhicule volant par exemple, sans pour autant préciser la façon dont il vole, attérri, etc. L'implémentation est l'affaire des classes. On peut alors par exemple facilement les regrouper dans un container, faire décoller tous les véhicules volants au même moment ou je ne sais quoi. Dans cet exemple pour qu'un véhicule puisse être considéré comme tel, il doit forcément pouvoir faire le plein puisqu'il est équipé d'un moteur (peu importe lequel). Il faut donc définir le remplissage du réservoir à carburant et surtout le niveau de carburant restant. On pourrait retrouver un contrat au niveau de la classe Vehicle pour imposer un carburant à base de pétrol par exemple puis durcir le contrat sur les classes en fonction du type précis de carburant nécessaire.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par RPGamer Voir le message
    C'est dommage qu'on s'éloigne du sujet de base mais je fais pleinement la différence entre une classe abstraite et une interface (qui est aussi abstraite par la force des choses). A mon avis ls interfaces ne sont pas plus Javaesques que la PpC est Eiffeilesque (petit retour au sujet )

    En ajoutant une implémentation dans ton interface, tu prives l'utilisateur de définir sa propre implémentation car la force de l'interface est justement d'imposer l'interface et non l'implémentation
    D'abord, on se rend compte, pour beaucoup d'interface, il est tout à fait possible de définir des comportements "par défaut" qui s'avèrent "suffisants et corrects" dans la plupart des situations, et, de plus, je ne vois absolument pas ce qui pourrait empêcher l'utilisateur de l'interface de (re)définir un comportement particulier pour les quelques cas où le comportement "par défaut" ne conviendrait pas.

    Dans la version non template que je donne, il n'y a en effet absolument rien qui interdise de déclarer les fonctions membres virtuelles, au même titre qu'il n'y a absolument rien qui nous oblige à les déclarer comme virtuelles pures

    Et, dans la version template, il n'y a absolument rien qui nous interdise de prévoir une spécialisation (partielle ou totale) en cas de besoin
    On va ainsi être capable de définir définir ce qu'est un véhicule volant par exemple, sans pour autant préciser la façon dont il vole, attérri, etc. L'implémentation est l'affaire des classes. On peut alors par exemple facilement les regrouper dans un container, faire décoller tous les véhicules volants au même moment ou je ne sais quoi. Dans cet exemple pour qu'un véhicule puisse être considéré comme tel, il doit forcément pouvoir faire le plein puisqu'il est équipé d'un moteur (peu importe lequel). Il faut donc définir le remplissage du réservoir à carburant et surtout le niveau de carburant restant. On pourrait retrouver un contrat au niveau de la classe Vehicle pour imposer un carburant à base de pétrol par exemple puis durcir le contrat sur les classes en fonction du type précis de carburant nécessaire.
    Mais qu'est ce qui nous empêche de définir un comportement par défaut si l'on est capable d'en trouver un qui puisse s'avérer "correct et suffisant" dans la majorité des cas

    En dehors de l'approche de la notion d'interface telle que présentée par java, la réponse est simple : absolument rien. Et c'est encore plus vrai dans le sens où C++ nous fournit énormément de possibilités qui n'existent pas en java pour obtenir un résultat équivalent!

  19. #19
    Membre averti Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    Mais qu'est ce qui nous empêche de définir un comportement par défaut si l'on est capable d'en trouver un qui puisse s'avérer "correct et suffisant" dans la majorité des cas
    C'est ce que je fais avec la classe abstraite Vehicle, c'est l'objectif de la classe abstraite (implémenter ce comportement commun qui consiste à faire le plein), qui n'est pas exactement le même objectif que les interfaces, comme on peut le constater dans cet exemple. C'est pour ça que je le trouve particulièrement bon pour mettre en avant cette différence. En outre, les interfaces offrent de l'évolutivité car il est directement admis qu'on ne connait pas tous les modes de propulsion possible. On permet donc tous types d'implémentations et on laisse cet responsabilité aux classes filles.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par RPGamer Voir le message
    C'est ce que je fais avec la classe abstraite Vehicle, c'est l'objectif de la classe abstraite (implémenter ce comportement commun qui consiste à faire le plein), qui n'est pas exactement le même objectif que les interfaces, comme on peut le constater dans cet exemple. C'est pour ça que je le trouve particulièrement bon pour mettre en avant cette différence. En outre, les interfaces offrent de l'évolutivité car il est directement admis qu'on ne connait pas tous les modes de propulsion possible. On permet donc tous types d'implémentations et on laisse cet responsabilité aux classes filles.
    Encore une fois, il semble que l'on soit plus ou moins d'accord sur le principal, bien qu'il reste quelques points de désaccord sur les détails

    Pour toi, un classe fournissant des comportements "par défaut" sera considérée comme une classe abstraite et ne sera pas une interface, alors que, de mon coté, je ne vois absolument pas pourquoi nous ne pourrions désigner ce genre de classe sous le terme d'interface

    Mais pour ma part, je fais surtout la distinction entre le fait qu'une classe soit spécifiquement destinée à servir de classe de base (par exemple : une classe Vehicle, dont hériteraient aussi bien des classes Plane, Submarine ou Car), qui ont de fortes chances d'être abstraites "par nature", et toutes les classes qui ne font qu'exposer des comportements susceptibles d'être ajoutés à certaines classes (plus ou moins) concrètes.

    Pour moi, tout ce qui entre dans cette deuxième catégorie mérite d'être désigné sous le terme générique d'interface, que les services exposés nous proposent une implémentation par défaut ou non.

    Et, attention! je ne dis pas qu'il est systématiquement possible de fournir un comportement par défaut pour les services exposés par une interface; loin de là Je dis juste que c'est juste une possibilité qui peut être envisagée si le langage utilisé le permet, et donc que la restriction du "aucun comportement n'est implémenté dans une interface" n'a de raison d'être que... dans le cadre d'un langage qui impose ce genre de restriction (comme java).

Discussions similaires

  1. Les meilleurs cours et tutoriels C++
    Par Community Management dans le forum C++
    Réponses: 1
    Dernier message: 13/05/2015, 14h50
  2. Obligatoire : lisez les règles du forum : MAJ 06/08/2010
    Par Anomaly dans le forum Mode d'emploi & aide aux nouveaux
    Réponses: 0
    Dernier message: 03/07/2008, 14h46
  3. Réponses: 5
    Dernier message: 20/08/2002, 19h01
  4. recherches des cours ou des explications sur les algorithmes
    Par Marcus2211 dans le forum Algorithmes et structures de données
    Réponses: 6
    Dernier message: 19/05/2002, 23h18
  5. Une petite aide pour les API ?
    Par Yop dans le forum Windows
    Réponses: 2
    Dernier message: 04/04/2002, 22h45

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