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 :

Pointeur de fonctions et exceptions


Sujet :

C++

  1. #1
    Nouveau Candidat au Club
    Profil pro
    Inscrit en
    Novembre 2005
    Messages
    1
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2005
    Messages : 1
    Points : 1
    Points
    1
    Par défaut Pointeur de fonctions et exceptions
    Bonjour a tous,

    J'essaie de faire une fonction qui retourne un pointeur de fonction d'une exception.
    Ce pointeur sera ensuite géré dans le main pour faire appel à la fonction.

    Malheureusement le compilo n'est pas d'accord, et il a du mal avec le type de la fonction (celle qui retourne le pointeur). Bref ,c 'est plus explicite avec du code


    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
     
    void lol() {
    	cout << "la fonction void\n";
    	}
     
    int selectionne(int exception) {
    	if (exception == 1) {
    	throw 3;
    	}
    	else if (exception == 2) {
    		char *coucou = "coucou";
    		throw (char*)coucou;
    		}
    	else if (exception == 3) {
    		float unfloat = 1.5;
    		throw unfloat;
    		}
    	else if ( exception == 5) {
    		void (*pt)() = &lol;
    		throw pt;
    		}
     
    	else {
    		throw true;
    		}
    	}
     
    char * travaille(void) {
    	int nb;
    	cin >> nb;
    	try { 
    	throw selectionne(nb);
    	}
    	catch (int lexception) {
    			cout << lexception << endl;
    			}
    	catch (char *lexception) {
    		return lexception;
    		}
    	catch (float lexception) {
    		cout << lexception << endl;
    		char* ok = "ok";
    		throw ok;
    		}
     
    		}
     
    void (*)()travaille(void) { //ligne 54
    	int nb;
    	cin >> nb;
    	try { 
    	throw selectionne(nb);
    	}
    	catch (void (*lexception)()) {
    		return lexception;
    		}
    }
    ce qu'en dit le compilo:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    exception.cpp:54: error: expected unqualified-id before ')' toke
    exception.cpp:54: error: expected initializer before "travaille"
    Alors je sais pas si c'est comme ça que ça doit être fait, c'est ma toute première fois avec les pointeurs de fonctions et les exceptions.

    Si quelqu'un peut m'éclairer

  2. #2
    Membre éclairé

    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    717
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 717
    Points : 858
    Points
    858
    Par défaut
    Tu trouvera la réponse ici.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut, et bienvenue sur le forum.

    Il y a quelques d'erreurs qui risquent de mener à des catastrophes et d'autres qui dénotent une mé-compréhension complète de certains concepts du C++.

    Cette réponse risque donc fortement d'être longue, mais je te conseille fortement de la lire dans son intégralité et de ne pas hésiter à poser des questions sur les points que j'aborderai que tu ne comprendrais pas.

    Une erreur qui revient régulièrement est le fait que tu confonde un pointeur sur caractères avec une chaine de caractères "C style" (un tableau de caractères terminé par '\0').

    Les lignes de code où apparaissent cette confusions sont entre autres:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    char *coucou = "coucou";
    ou
    En effet, un pointeur n'est en définitive qu'une variable "particulière" en cela qu'elle contient... l'adresse à laquelle le compilateur va trouver l'information réelle.

    Il est vrai que, lorsque tu déclare un tableau "C style" sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int tab[10]; /* j'aurais pu utiliser n'importe quel type autre que int
                  */
    /* ou faisant appel à l'allocation dynamique de la mémoire */
    int * tab = new int[unNombre];
    le nom du tableau (tab, dans l'exemple) représente un pointeur pointeur sur le premier élément du tableau (celui qui se trouve à l'index 0), mais c'est parce qu'un tableau représente un ensemble d'éléments contigus en mémoire et que, pour pouvoir accéder à l'ensemble des éléments, il faut commencer par pouvoir accéder... au premier

    La gestion dynamique de la mémoire et la manipulation des pointeurs sont clairement les deux cas les plus "peau de banane" que l'on puisse trouver en C, et, quand en plus il s'agit de gérer en réalité des chaines de caractères (pour lesquelles il faut faire appel à des fonctions particulières sous peine d'avoir une chaine qui ne sera jamais considérée comme terminée) il faut vraiment apporter la plus grande attention à ce que l'on fait, sous peine de voir son programme "partir en vrille intégrale".

    C'est pourquoi le premier conseil que l'on donne est de préférer en toute circonstances les possibilités offertes par le C++ à toute alternative issue du C.

    C'est à la fois plus sécurisant à l'emploi et de nature à te simplifier énormément la vie: tu peux arriver à éviter l'usage de pointeurs et le recours à l'allocation dynamique à de rares cas clairement déterminés qui ont généralement trait à l'utilisation de l'héritage et à la mise en oeuvre de ce que l'on appelle le polymorphisme

    Ainsi, le langage fournit une classe nommée string, disponible dans l'espace de noms std par simple inclusion du fichier d'en-tête <string>, qui représente l'outil "idéal" pour la gestion des chaines de caractères.

    Tu trouvera la réponse à une bonne partie des questions que tu risque de te poser à leur sujet sur la page de la FAQ qui leur est dédiée

    Dans le même ordre d'idée, je te conseillerais (bien que le problème n'apparaisse pas dans le code que tu présente) de t'intéresser aux différentes collections d'objets proposée par la STL et de les préférer en toute occasions aux possibilités issues du C que sont la gestion de tableaux dynamique ou la création de structures personnelles telles que piles, files, listes, ou arbres binaire / n aires...

    Tu trouvera beaucoup d'informations au sujet de la STL sur la page de la FAQ qui lui est dédiée, dont un diagramme te permettant de choisir le conteneur le plus adapté à l'usage que tu prévois d'en faire.

    Ensuite, nous pourrions nous intéresser quelque peu à ta fonction selectionne, et à la succession de tests sur la valeur de l'argument expression...

    Une succession de if ... else if n'est clairement pas mauvaise, mais, lorsqu'il s'agit de comparer une variable à des valeurs numériques entières, l'idéal reste toujours d'utiliser ce que l'on appelle le "test à choix multiple", et qui donnerait quelque chose 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
    void selectionne(int exception)
    {
        switch (exception)
        {
            case 1:
                /* ce qu'il faut faire */
                break;
            case 2 :  
                /* ce qu'il faut faire */
                break;
            case ... :  
                /* ce qu'il faut faire */
                break;
            case n :  
                /* ce qu'il faut faire */
                break;
            default :  
                /* ce qu'il faut faire si ce n'est aucune des valeurs
                 * envisagées
                 */
                break;
     
        }
    }
    Ce point n'est qu'un détail car, à l'exécution, le résultat sera identique à l'utilisation de if ... else if en cascade, outre une lecture plus facile du code, il permet au compilateur certaines optimisations, comme le fait de ne pas remettre la valeur testée à chaque test dans un registre du compilateur (tu peux gagner quelques fréquences d'horloge en temps d'exécution) .

    Le dernier point qu'il me semble utile d'aborder, c'est tout ce qui a trait à la gestion des exceptions, et ce sera un gros morceau...

    Commeçons peut-être par expliquer, de manière succinte (et donc, forcément pas tout à fait juste) ce qu'est une exception:

    Il s'agit d'un "événement" qui survient lorsque le "système" est dans un état incohérent, et qui va empêcher le système de fonctionner correctement.

    Le programmeur espère que l'événement en question n'arrivera ...qu' exceptionnellement, mais prévoit qu'il peut survenir et se rend compte que, pour traiter les causes qui ont fait que le système est dans un état incohérent, il est face à l'obligation de "remonter" la pile d'appel des fonctions qui ont mené à l'événement.

    Il n'y a donc aucune raison de lancer une exception si... le système est dans un état cohérent et qu'il peut fonctionner correctement

    Parmis les événements qui risquent de survenir les plus récurrents et qui peuvent nécessiter le lancement d'une exception (bien que ce ne soit pas obligatoire dans tous les cas), on peut citer, sans être exhaustif:
    • L'impossibilité d'accéder à un fichier (parce que inexistant, protégé en écriture ou "verrouillé" par ailleurs)
    • L'impossibilité d'obtenir suffisamment de mémoire pour contenir l'ensemble des informations
    • une tentative d'accès "hor limites"(essayer d'accéder à un élément N alors que le conteneur n'en contient que ... N-1)
    • le fait de ne pas trouver un élément recherché dans une collection donnée
    • La tentative de modification d'une valeur ne pouvant être modifiée que si elle est dans un état particulier permettant cette modification
    • ...
    Il faut savoir que, lorsqu'une exception est lancée:
    1. les instructions qui suivent le moment où l'exception est lancées ne sont pas exécutées
    2. le destructeur de toutes les ressources déclarées "statiquement" (sans avoir recours au pointeur et à l'allocation dynamique de mémoire) de toutes les portées que l'exception traverse sont automatiquement appelés
    3. l'exception "remonte" la "pile d'appel" jusqu'à ce qu'elle soit attrapée, et qu'elle ne soit pas relancée
    Je reviendrai sur le (3) en temps utile, mais, le (1)et le (2) signifie qu'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
    void foo()
    { 
        /* j'ouvre un fichier en lecture, par exemple */
        std::ifstream ifs("monFichier.txt");
        /* et je déclare un tableau (C style) statique de 10 entiers */
        int tab[10];
        /* je manipule mon tableau et mon fichier 
         * ...
         * mais, à un moment, ce que je craignais arrive
         */
         if (incoherence quelconque) /* (*) */
            throw MonException(); /* il vaut mieux travailler avec des exceptions
                                   * qui sont des objets (****);)
                                   */
     
        /*  ... (***) */
    }/* (**) */
    (*) dans "la majorité des cas", nous déciderons de lancer une exception parce que l'on constate que le système est dans un état incohérent... cela
    se traduit le plus souvent par un... test de la cohérence des données impliquées


    (**)La norme précise que la "durée de vie" d'une variable déclarée statiquement (sans avoir recours aux pointeurs et à l'allocation dynamique (new) ) s'étant jusqu'à ce que l'on quitte la portée dans laquelle elle a été déclarée, autrement dit jusqu'à l'accolade fermante de même niveau que l'accolade ouvrante dans laquelle la variable est déclarée

    Lorsque l'on atteint l'accolade fermante (**), le destructeur de la classe ifstream est donc automatiquement appelé. Grâce au respect du principe inverse du RAII (inverse dans le sens ou la destruction doit servir de libération des ressources), je suis sur que mon fichier est fermé lorsque je quitte la fonction (que je passe au niveau de l'accolade fermante).

    (***) tout ce qui peut se trouver ici sera effectué s'il n'y a pas eu d'incohérence et que l'exception n'a donc pas été lancée.

    Par contre, si le test de cohérence des données (*) met en évidence le fait que les données sont incohérentes, l'exception est lancée et ce qui se trouve après ne sera pas exécuté: c'est "un peu comme si" toutes les accolades ouvertes étaient fermées aux moment de lancer l'exception.

    Nous sortons donc de la portée de la fonction (et de toutes les "sous portées" de boucles et de tests) et toutes les variables qui ne sont pas le fruit d'une allocation dynamique de la mémoire sont détruites...

    (****) est encore un sujet volumineux...

    Tu l'auras (sans doute) compris, les exceptions sont lancées dans certains contexte...

    Cela implique que, pour pouvoir gérer efficacement une exception qui se produit, il faut savoir... dans quel contexte elle s'est produite, car il n'est pas rare que ta logique fasse plusieurs tests de cohérence qui peuvent chacun mener à une exception différente...

    Si je modifie un peut le code précédent, il pourrait devenir 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
    void foo()
    { 
        /* j'ouvre un fichier en lecture, par exemple */
        std::ifstream ifs("monFichier.txt");
        /* je n'ai pas su ouvrir mon fichier 
         *  je signale donc que je n'ai pas trouvé le fichier */
        if(!(ifs)
            throw FileNotFound(); /* (1) */
        /* et je déclare un tableau (C style) statique de 10 entiers */
        int tab[10];
        /* je vais lire un entier */
        int lecture;
        /* et j'ai besoin d'un compteur car je ne peux lire que 10 entiers 
         * maximum 
         */
        size_t num = 0;
        /* pour que tout soit bien clair, j'ai un booléen qui m'indique
         *  si je dois tenter encore une lecture
         */
        bool encore =true;
        while(encore)
        {
            /* je tente de lire mon entier, pas de bol, j'obtiens autre chose */
            if(! (ifs>>lecture) )
                throw BadFileFormat();/* je lance une exception indiquant
                                       * que le fichier n'est pas cohérent avec 
                                       * ce que j'attends (2)
                                       */
            /* Quel que soit le nombre de valeurs lues, la lecture s'arrête si je
             * croise la valeur (arbitraire, définie dans les spécifications du
             * fichier) 3999... !!! on peut ne jamais croiser cette valeur !!!
             */
            if(lecture == 3999)
                encore = false;
            /* on place la valeur lue dans le tableau */
            tab[num] = lecture;
            /* et on incrémente le compteur */
            ++num;
            /* mais on ne peut pas accéder en écriture à l'indice 10...
             * si nous devons encore lire une donnée et qu'on est à l'indice
             * 10, il faut indiquer que le fichier contient trop de valeurs
             */
           if(num == 10 && encore)
               throw TooManyValues(); /* (3) */
        }
        /* La récupération s'est bien passée, merci, je continue */
    }
    Chacune des exceptions lancées ( (1), (2) et (3) ) dans ce code représente un contexte différent, nécessitant une intervention (potentiellement) différente en vue de résoudre le problème.

    Or, si, lorsque tu invoque une fonction, le contexte dans lequel tu le fais est facilement représentable (de par l'instance sur laquelle tu invoque la fonction ou de par les paramètres que tu fourni à la fonction), le seul moyen dont tu dispose pour transmettre le contexte dans lequel l'exception est survenue au fonctions qui ont mené à la survenue de l'exception, c'est... par l'exception elle-même...

    De plus, il est parfois utile d'ajouter des informations complémentaires au contexte dans lequel l'exception a été lancée...

    Le meilleur moyen pour être sûr que l'exception "véhicule" toutes les informations jusqu'au point où l'on sera en mesure de traiter le problème, c'est... de faire en sorte que l'exception soit en mesure de "contenir" ces information

    Le meilleur moyen d'y arriver est donc de créer une structure (ou une classe) qui disposera des membres adéquats

    De plus, l'héritage aidant, il sera possible d'envisager une gestion "générique" propre à "l'erreur de lecture" en faisant, si besoin, dériver ces trois exception d'une structure de base que nous pourrions nommer, par exemple "FileReadError" et qui pourrait tout à fait disposer de comportements polymorphes

    Et c'est, du fait de cette possibilité et de par le fait que les objets "créés statiquement" sont détruit lorsqu'on sort de la portée dans laquelle ils sont créés que la "bonne pratique" est de lancer une exception par valeur (tu remarquera que je lances mes exceptions sans avoir recours à l'allocation dynamique) et récupérées par référence.

    Il est maintenant temps de revenir sur mon
    (3) l'exception "remonte" la "pile d'appel" jusqu'à ce qu'elle soit attrapée, et qu'elle ne soit pas relancée
    Si on lance une exception, c'est dans l'espoir d'être en mesure de traiter l'incohérence du système à un moment ou un autre et, généralement, parce que nous ne sommes pas en mesure, au moment où cette incohérence est remarquée, d'y remédier...

    Seulement, il arrive régulièrement que nous ne puissions apporter, à un moment donné, qu'une solution partielle qui ne peut être apportée qu'à ce moment particulier...

    Le principe est toujours le même: tu ne peux manipuler une variable que... quand tu la connais, et, si tu dois le faire afin de replacer ton système dans un état cohérent (suite au lancement d'une exception), tu *risque* de devoir faire appel à des informations qui ne sont accessible que dans une fonction particulière, ni dans les fonctions appelées par cette fonction particulière qui ont provoqué le lancement de l'exception, ni dans les fonctions qui ont mené à appeler cette fonction particulière dans laquelle trouve les informations permettant d'apporter une solution partielle au problème.

    L'idée est donc de s'organiser de manière à ce que les fonctions dans lesquelles tu ne dispose pas des informations nécessaires à la solution du problème laissent "purement et simplement" remonter l'exception vers la fonction qui les a appelées, et que, les fonctions qui sont en mesure d'apporter une solution (même partielle) au problème veillent à récupérer l'exception, traitent la partie à laquelle elles peuvent apporter une solution, et, si le système n'est toujours pas cohérent, renvoie l'exception (éventuellement modifiée) vers la fonction qui les a appelées...

    Le tout dans l'espoir que le système soit redevenu cohérent avant de quitter la fonction principale, car autrement cela signifie une "plantage" pur et simple de l'application avec, sous windows le "zoli message proposant d'envoyer un rapport d'erreur à microsoft"

    [EDIT](, je me suis trompé de bouton )
    Ainsi, tu pourrais très bien envisager les quelques fonctions suivantes:
    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
    void bar()
    {
       /* nous savons que foo() peut lancer trois exceptions différentes,
        * mais nous ne sommes pas en mesure de les gérer,
        * nous laissons donc les exceptions remonter gentiment 
        */
        foo();
    }
    void autreFonction()
    {
        /* ici, nous sommes en mesure de gérer une partie de l'exception
         * TooManyValues et la totalité de BadFileFormat
         * nous prévenons le compilateur qu'il doit s'attendre à ce qu'une
         * exception soit lancée
         * Comme les autres exceptions ne seront pas attrapées, elles
         * remontenront gentiment
         */
        try
        {
            /* il y a peut etre à faire avant */
            bar();
           /* ... et après (si ca s'est bien passé */
        }
        /* nous récupérons TooManyValues  */
        catch(TooManyValues & e)
        {
            /* nous traitons ce que nous pouvons et nous la relançons
             * pour que la fin de la solution soit apportée là ou ca peut
             */
           throw e();
        }
         */ nous récupérons BadFileFormat
        catch (BadFileFormat &e)
        {
            /* nous la traitons, et comme le système est cohérent, nous ne la
             * relançons pas
             */
        }
     
        /* Ce qu'il faut faire qu'il y ait eu ou non une exception BadFileFormat
         * (mais qui ne sera pas exécuté si une autre exception est survenue)
         */
    }
    void deuxiemeFonction()
    {
        /* on ne sait rien traiter ici */
        /* ce qu'on doit faire avant de risquer le problème */
        autreFonction();
        /* ce qu'on doit faire s'il n'y a pas eu de problème */
    }
    void troisiemeFonction()
    {
        /* ici, nous pouvons apporter la fin de la solution à TooManyValues 
         * si d'autres exceptions arrivent ici, elle remonteront encore
         */
        try
        {
            /* on ne sait rien traiter ici */
            /* ce qu'on doit faire avant de risquer le problème */
            deuxiemeFonction();
            /* ce qu'on doit faire s'il n'y a pas eu de problème */
        }
        catch(TooManyValues & e)
        {
            /* nous la traitons, et comme le système est cohérent, nous ne la
             * relançons pas
             */
        }
        /* Ce qu'il faut faire qu'il y ait eu ou non une exception TooManyValues 
         * (mais qui ne sera pas exécuté si une autre exception non encore
         * traitée est survenue)
         */
    }
    int main()
    {
        /* !!!dernier rempart avant le plantage!!! */
        /* des choses qui ne risquent absolument pas de poser problème
        try
        {
            /*peut etre d'autres choses */
            troisiemeFonction();
            /* tu commence à comprendre le principe ;) */
        }
        /* Nous pouvons nous assurer de prendre toutes les exceptions
         * FileReadError, car il y a peut etre d'autres fonctions qui
         * lancent des exceptions similaires, sans être en mesure de les traiter
         */
        catch(FileReadError &e)
        {
            /* Ce que l'on peut encore traiter ...
             * il vaut mieux ne pas relancer l'exception sous peine de plantage
             * par contre, nous pouvons faire sortir le programme "proprement"
             * sur une erreur
             */
            return 1;
        }
        /* il y a peut etre quelque chose à faire, juste avant de quitter 
         * l'application
         */
        return 0;
    }
    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

Discussions similaires

  1. opengl et pointeur de fonction
    Par Oldhar dans le forum C
    Réponses: 5
    Dernier message: 06/11/2003, 23h56
  2. Declaration de fonction retournant un pointeur sur fonction
    Par pseudokifaitladifférence dans le forum C
    Réponses: 5
    Dernier message: 11/08/2003, 19h37
  3. Matrice de pointeurs de fonctions
    Par sebduth dans le forum C
    Réponses: 15
    Dernier message: 18/07/2003, 14h03
  4. [Kylix] Pointeur de fonctions
    Par _dack_ dans le forum EDI
    Réponses: 1
    Dernier message: 03/07/2003, 10h17
  5. pointeur de fonction
    Par kardath dans le forum C
    Réponses: 4
    Dernier message: 28/12/2002, 14h39

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