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 :

Erreur d'héritage, membre non trouvé


Sujet :

C++

  1. #1
    Membre expérimenté
    Avatar de Luke spywoker
    Homme Profil pro
    Etudiant informatique autodidacte
    Inscrit en
    Juin 2010
    Messages
    1 077
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Etudiant informatique autodidacte

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 077
    Points : 1 742
    Points
    1 742
    Par défaut Erreur d'héritage, membre non trouvé
    Salut les C++,

    Cette nuit j'ai tenté de coder une mini-classe de base (un membre, une méthode) dont d'autres classes devaient hériter.
    Et j'ai codé, naturellement sans me poser de questions, pratiquement le même bout de code suivant, au type du membre et de la méthode près :

    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
    class Base_Test {
     
      public :
     
        int m_int ;
     
        Base_Test() {} ;
     
        int get_int() { return m_int ; } ;
    } ;
     
    class Test : public Base_Test {
     
      public :
     
        Test(const int integer) ;
    } ;
     
    Test::Test(const int integer) : m_int(integer) {
     
      /** etc.. **/  
      ;
    }
    Et le compilateur (g++ -std=c++11 testit.cpp ) a renvoyé le message d'erreur suivant :

    testit.cpp: In constructor ‘Test::Test(int)’:
    testit.cpp:19:33: error: class ‘Test’ does not have any field named ‘m_int’
    Test::Test(const int integer) : m_int(integer) {
    Bon. Étant débutant mais quand même (3 mois de C++) je me suis dit que si les 23 lignes qui sont censées définir une classe dont une autre hérite me viennent pas naturellement je devais avoir un problème avec l'héritage de classe défini personnellement, ce pourquoi je poste.

    Car je me sens frustré de cet mini échec, vous me direz c'est pas compliqué : merci, je sais. J'ai déjà fait ça, de l'héritage avec du C++, notamment des jeux avec gtkmm entre autres.

    J'aurais pu rapidement trouver la solution à mon problème tout seul mais je me soucie du mauvais réflexe ou plutôt de ce qui manque, je suppose, dans la définition des classes.

    Alors je voudrais que vous m'aidiez à comprendre ce qui ne va pas dans mon code (réflexe) car si je dois coder une dans une situation d'héritage je referai la même erreur, je pense, si vous ne me corrigez pas. Ce pourquoi je poste.

    Merci pour votre aide précieuse.



    Une autre chose plus intéressante à débattre:

    J'aimerais savoir (ou avoir votre avis) sur le fait de soit:

    1. mettre une variable en membre private et implémenter des accesseurs.
    2. mettre une variable en membre en public et y accéder directement.

    Car j'ai souvent lu | vu qu'il faillait préférer la première solution.

    Il n'y a aucun problème pour moi, mais j'aimerais savoir d'où vient ce conseil ou plutôt quel sont les avantages de la première solution.
    Il faut dire que je n'ai pas encore lu de bouquin sur comment coder correctement ou de manière optimiser en C++: les bon usages brefs.

    Alors est-ce:

    1. Pour des question de clarté de code ?
    2. Pour des raisons de performances ?
    3. Se protéger contre soit-même (c'est débile) ?
    4. Ce sont les usages ?

    Bref : pourquoi se compliquer la vie avec 2 méthodes de plus (qui sont souvent définies dans la déclaration de la classe, donc considérées comme des méthodes inline par le compilateur) et qu'a l'espace private de plus ?

    J'aimerais comprendre le pourquoi du comment, bref ce qui se cache sous le capot.

    Merci pour vos réponses éclairées et l'aide apportée.

    Bon code C++ à vous.

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    m_int est membre de la classe de base, elle-même membre de la classe héritée.
    Tu ne peux pas l'initialiser ainsi, car il a déjà été initialisé par le constructeur par défaut.

    Tu ne peux plus que le réinitialiser.

    C'est pourquoi tu voudras probablement définir un constructeur tel que Base_Test(int i) : m_int(i) {}, qui sera protected ou public, selon le reste des besoins.

    Et ton constructeur de classe héritière sera ainsi fait:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test::Test(int integer) : Test_base(integer) {
        //...
    }
    Note que tu n'as pas besoin de définir l'argument comme constant si tu le prends par copie.

  3. #3
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    Pour les questions de private/public.

    Il faut bien distinguer le développeur qui conçoit une classe de celui qui l'utilise, ca peut être la même personne, mais pas avec le même objectif.

    L'interface publique est extrèmement sensible, c'est avec elle que sera écrit le code utilisant ta classe.
    L'espace privé permet de faire tout ce que tu veux, sans crainte de casser du code si tu dois changer.

    Une variable membre publique n'est pas maitrisée par la classe. Possédée, oui, mais pas maitrisée.

    Une autre distinction, c'est que les fonctions publiques disent ce que l'objet fait, les services qu'il rend.
    Tandis que les fonctions privées expriment des détails de comment il le fait.

    Tu n'as aucune envie de savoir que pour poster ta réponse, tu vas devoir passer par des requêtes HTTP, DNS, des protocoles comme IP, TCP ou UDP, Ethernet etc.
    Par contre, le fait de pouvoir mettre du texte en gras, en italique, ou du code, ça, oui, ça t'intéresse.

    Une classe (au sens POO) sert à masquer des détails techniques pour s'intéresser à un problème de plus haut niveau.
    Par contre, c'est vrai, une classe peut aussi servir de "paquet de données cohérentes", à la façon d'une struct en C.

    Je précise en C, car en C++, struct et class sont quasiment synonyme. Et même parfaitement synonyme si tu précises systématiquement (pour lisibilité) les private: etc

  4. #4
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 938
    Points : 7 347
    Points
    7 347
    Par défaut
    Je ne m'y connais pas trop en C++, mais ton code selon les différents modèles que j'ai pu voir pourrait prendre cette forme

    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
    #include <iostream>
     
    using namespace std;
     
    class Base_Test {
     
      public :
     
        int m_int ;
        Base_Test() {} ;
     
        int get_int() { return m_int ; }
    };
     
    class Test : public Base_Test {
     
    public:
     
        Test(const int);
     
    } ;
     
    Test::Test(const int integer) : Base_Test() {
     
      /** etc.. **/
     
      ;
    }
    Il n'y a aucun problème pour moi, mais j'aimerai savoir d'ou vient ce conseil ou plutôt quel sont les avantages de la première solution.
    Avantage, non, mais un intérêt plutôt conceptuel je pense, dans le sens où les membres privés ne sont pas accessibles depuis du code en dehors de la classe.
    Pour l'héritage, franchement ça me gênerait, et je choisirais plutôt à utiliser des membres protégés (protected), inacessibles en dehors d'une classe, mais équivalents à des membres publics. Enfin bref, lire cela...

    C'est sans doute une question de performance aussi de ne pas utiliser les membres privés quand on fait de l'héritage, car moins flexible...

    En ce qui me concerne, il est logique que tu te poses cette question dans le cas de l'héritage, les membres privés étant contraignant dans ce cas de figure.

    Je pense avoir dis ce que je peux selon ma faible expérience en C++

  5. #5
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    Dans le cadre de l'héritage, il convient de se poser la question parfois délicate de pourquoi faire de l'héritage.

    Une classe est-elle pensée pour servir de classe de base? si oui, il faut réfléchir à ce qu'elle propose.
    En C++, on dispose de deux formes d'héritages (et de moyens de ne pas le faire du tout ).

    L'héritage public, ou apparant, sert à exprimer le fait qu'un Bidule est aussi un Truc, et que tout ce qui accepte un truc peut tout aussi bien accepter un Bidule. Si ca n'est pas le cas, alors soit Bidule, soit Truc est mal conçu.
    Et la réponse est pratiquement toujours que c'est Bidule qui n'aurait pas dû être un Truc.

    L'héritage privé, ou technique, sert à hériter d'une classe technique.

    Une classe Logger qui sert à faire des logs aurait intérêt à paraitre comme un std::ostream. Cela permettra à chaque utilisateur de ne pas redéfinir des fonctions spéciales pour logger ce qui leur plaira. Du moins, pas une seconde série à cote de celle pour un fichier ou le flux standard.

    Un héritage privé souvent une alternative à une composition (le fait d'avoir un membre), mais n'est absoluement pas perceptible par l'utilisateur de la classe.
    En général, toute fois, l'héritage n'est pas conseillé.

    Pour être tout à fait honnête, il existe une troisième forme d'héritage, l'héritage protected. Cela permet aux classes héritières de la classe héritante (les petites filles, donc), de savoir que l'héritante est construite autour de la classe de base, mais cache ce même fait aux simples utilisateurs.
    Je n'en connaissais aucun usage jusqu'a présent. Je viens de découvrir que boost::compressed_pair s'en sert.

  6. #6
    Membre expérimenté
    Avatar de Luke spywoker
    Homme Profil pro
    Etudiant informatique autodidacte
    Inscrit en
    Juin 2010
    Messages
    1 077
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Etudiant informatique autodidacte

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 077
    Points : 1 742
    Points
    1 742
    Par défaut
    Merci les gars pour les nombreuses réponses et explications que vous avez fournis.

    Concernant le premier problème, le problème d'héritage, j'ai pus le résoudre en quelques minutes quand j'ai repris le code.
    Disons que j'ai un peu flipper quand j'ai vue que je rencontrai des problèmes avec quelques lignes de code vraiment basiques et que j'ai fait un flan pour rien quand j'ai remarquer ce mauvais réflexe qui est en faîte une erreur de ne pas initialiser le membre dans le constructeur de la classe de base et avoir fait l'erreur de ne pas appeler celui-ci dans la classe héritée...

    Mais ça a eu l'avantage de susciter chez vous de nombreuse réactions concernant l'héritage, vos réponses sont fort intéressantes, merci.

    Par contre je n'ai eu que peu de réponses concernant le deuxième débat ou du moins pas celle que j'attendais.

    Vous avez donc dit si je résume que l'espace private permet de masquer certains membres ou méthodes de classe dans les tout les cas mais surtout pour l'interface de la classe: private on touche pas et c'est sur. Par contre publique c'est ce qui est faisable avec la classe et du coup moins sur.

    Mais apparemment personne n'a entendus parler de ce conseil de codage de préféré les membres privée avec getters et setters plutôt que un membre a accès directe déclaré dans l'espace publique...???

    Car un getter ou setter peu faire aussi autre chose que de simplement affecter ou renvoyer la valeur d'un membre:

    Contrôle de marge maximale et minimale de valeurs par exemple
    ou ce qui va influer sur les autres membres de ce changement de valeur dans le cas d'un setter.


    Donc si ce n'est pas si répandus que ça je ne dois pas m'inquiéter de ce qui se cache derrière ce conseil.

    Je pensais seulement que c'était un conseil comme d'éviter le plus possible les variables globales en C.
    Plutôt passer des pointeurs de fonction en fonction par exemple a la place (a ne pas faire en C++ je pense).
    PS: j'ai récemment découvert le fond de la notion de fonction inline:

    Le mot-clef inline suggère au compilateur de copier et d'exécuter le code de la fonction inline directement a l'endroit ou la fonction inline est appelée a la place de placer la fonction sur la pile comme une fonction classique.

    Quel sont les avantages: éviter un empilement.

    Quand utiliser le mot-clef inline: pour de petites fonctions que l'on appelle souvent dont le code peut être remplacer par quelques lignes de codes analogue a l'endroit de l'appel.

    Quand ne pas utiliser le mot-clef inline: par exemple pour des fonctions récursives, vous comprendrez sûrement pourquoi.

    J'en vient au faîte:

    Existe-t-il une manière de faire savoir au compilateur qu'une variable va souvent changer de valeur afin que le compilateur puissent optimiser en conséquence ?

    Et si oui quel est le nombre moyen de changements de valeur acceptable afin de signifier cela au compilateur par rapport aux nombre d'instructions ?
    Car sinon l'on en mettrai dans toutes les variables de compteurs de boucles...

    Au cas ou vous ne le sauriez pas: il existe des types (contenus dans inttypes.h en C mais présent par défaut en C++) qui sont bien pratiques:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    int8_t    == char
    uint8_t   == unsigned char
     
    int16_t   == short
    uint16_t  == unsigned short 
     
    int32_t   == int
    uint32_t  == unsigned int
    Connaissez vous d'autres typedef autant pratique et que pratiquement tous les éditeurs de texte colorent (le mien en tout cas oui et la coloration syntaxique de developpez.net aussi) ce qui n'est vraiment pas le cas de tous les typedef...

    Sur ce bon code C++ a vous.

  7. #7
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    Dans le long débat, très sujet à trolls, du get/set face à la variable publique, on a diverses positions, qui se tiennent.

    La réponse ultra prudente:
    Toute variable membre est privée, sa visibilité est gérée par celle de ses get/set.
    L'intérêt premier est de pouvoir établir des controles, notamment dans la fonction set.
    Ainsi, tu pourrais avoir une classe de nombre pair, dont le set serait écrit ainsi: void setValeur(T i) {internal = i - i%2;}Si jamais il faut subitement ajouter un controle, il n'y a pas besoin de modifier tout le code pour introduire des fonctions.

    get et set ne devrait pas faire de controles
    Dans l'idée, get et set ayant une signification précise en langue humaine (ici, l'anglais), il doivent la respecter.
    De la même manière que Integer operator+(Integer, Integer) est priée de retourner la somme des valeurs que représente ses arguments.

    Du coup, get+set publiques signifie une variable publique.

    get et set sont souvent des erreurs de conception
    Si effectivement ils ne font pas de controles, et sont publiques, alors ce sont des obstacles à la lisibilité.
    Et souvent, d'autres noms sont plus parlant.
    Prenons une classe de point. On a deux considérations possibles:
    • la posture mathématique: un point est une valeur. Il n'y a pas de fonction de mutation. C'est une structure ouverte, généralement passée par référence constante.
    • le point de vue 2D/3D: un point est un objet, qui peut se déplacer (vertex).

    Dans ce second cas, getX() et getY() sont passablement intéressant (même si je les appellerai plutot x() et y()). Par contre, on ne voudra pas de setX ou setY. On préfèrera moveTo(Point), moveTo(x, y) et moveBy(Vector), moveBy(dx, dy). (moveTo pouvant aussi être nommée placeAt).

    Les getters restent assez intéressants, pour des raisons concrêtes de facilité d'écriture du reste du code.
    Mais je préfère ne pas mettre le mot get, pour ne pas induire que la variable est présente.

    Tu l'auras compris, ma position est de ne pas avoir de setteurs explicite.
    Une classe est soit une valeur (pas de setteurs, mais constructeur et opérateur=), soit une entité, ayant une plus grande sémantique qu'un paquet de variable.

  8. #8
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    En effet, pour inline, le compilateur fait comme il l'entend, et ca lui permet d'éventuellement ne pas générer de fonction.
    Le gros avantage est autre.
    Une fonction (libre ou non) définie (et pas juste déclarée) dans un en-tête peut être incluse dans plusieurs unités de compilation, brisant ainsi la règle de la définition unique.
    Si toutefois, la fonction en question est définie inline, il en est autrement.
    L'astuce magique, c'est que toute fonction membre définie dans la définition de la classe est automatiquement inline.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct Bidule {
        int f() const {return 2;}// cette fonction est inline car définie dans la classe.
        int g() const;
    };
    inline int Bidule::g() const {return 3;} //cette définition requiert le mot inline, car elle est définie hors de la classe.
    Pour les variables, le compilateur saura mieux que toi ce qu'il convient d'optimiser, il le fait déjà tout seul.
    Par contre, tu peux déclarer une variable comme non optimisable, avec volatile.
    Je crois que de cette façon, on peut garantir qu'elle sera matérialisée dans le binaire généré. Mais je n'en suis pas certain, n'ayant jamais eu à m'en servir.



    J'en profite pour noter un détail dans le conseil sur les variables globales.
    En C++, tu disposes de deux outils majeurs: la référence constante, et le destructeur.
    Le premier va te permettre de ne pas polluer ton code avec des étoiles de partout (par rapport au pointeur), sans pour autant copier des grosses choses à répétition.
    Le second permet de mettre en œuvre le RAII.

    Le RAII, c'est un moyen quasi magique de poser une garantie.
    Si tu veux que chaque fois que tu fais l'action 1, l'action 2 doive être fait une et une seule fois, la façon la plus sûre est de procéder ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct Bidule {
        Bidule() { /* ici l'action 1 */ }
        ~Bidule() { /* ici l'action 2 */ }
    };
    Pour faire l'action 1, il suffit de déclarer une variable. Automatiquement, quand on sort (de n'importe quelle manière) du bloc l'ayant déclarée, l'action 2 sera faite.
    Et comme personne n'appelle jamais un destructeur, elle ne sera pas faite deux fois.

    Voici quelques exemples de cette technique: std::string désalloue son tableau, std::list libère chaque noeud, std::fstream ferme son fichier, etc.
    Et le meilleur pour la fin: std::unique_ptr libère son pointeur. Grace à ce dernier, tu ne devrais plus jamais écrire delete.

    Tu peux faire de même dans plein de cas: toutes les fermetures et libérations: socket, connexion à une base de donnée, flux audio.
    Mais aussi un protecteur pour certaines bibliothèques. Par exemple, la SDL utilise une paire de fonctions SDL_init et SDL_quit.
    Autant en faire une capsule RAII.

    Un temps, j'avais même fait une template pour autoraiier un appel de fonction.
    Ca donnait d'abord ceci: auto raii_sdl = make_raii(&SDL_quit), puis carrément RAII<&SDL_quit> sdl_close;

  9. #9
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Pour le public/privé+accesseurs :
    Je considère qu'il y a deux catégories de classes :

    1/ Des classes qui ne servent qu'à agréger des données ensemble (par exemple une classe Citizen dans un système de gestion des passeports, c'est un nom, un prénom, une photo...), elles ont généralement peu d'invariants (sauf ceux issus directement des données membres), très peu de fonctions membres (souvent, juste un constructeur), et dans ce cas là, le plus simple et le plus clair est de mettre ses données en public.


    2/ Des classes qui ont un comportement (par exemple, une classe Citizen dans un jeu de simulation de ville à la simcity, qui va agir pour satisfaire différents besoins), elles ont généralement plein de fonctions, et des invariants propres à respecter. Dans ce cas, toutes les données doivent être privées (sinon, comment faire respecter les invariants ?).
    Après, je déteste la philosophie derrière la notion d'accesseur : On ne part pas des données membre pour en déduire des fonctions de l'interface. On conçoit l'interface de la classe le mieux qu'on peut, pour qu'elle ait du sens, sans se préoccuper des données membres derrière. Il se trouve que certaines de ces fonctions accèdent directement à des données membre, mais c'est plus un hasard qu'autre chose. Et j'ai souvent remarqué que moins il y en avait, mieux étaient mes classes.

    Quelqu'un ici a sorti une analogie que j'aime beaucoup (Luc ou Jean-Marc, je ne sais plus trop). C'est celle de la machine à laver et de la laverie. Une machine à laver expose des données membre : On peut régler la température, la vitesse d'essorage, on y met de l'assouplissant... La laverie expose un service : On donne du linge sale, on récupère du linge propre. Ce second type de classe est plus simple à utiliser, et évite les erreurs.

  10. #10
    Membre expérimenté
    Avatar de Luke spywoker
    Homme Profil pro
    Etudiant informatique autodidacte
    Inscrit en
    Juin 2010
    Messages
    1 077
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Etudiant informatique autodidacte

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 077
    Points : 1 742
    Points
    1 742
    Par défaut
    Merci pour vos réponses fort intéressantes en conception d'interface de classe d'un point de vue technique,
    comme d'un point de vue théorique.

    Et pour les quelques astuces présentés, je n'ai malheureusement que très peu compris le point suivant:
    Un temps, j'avais même fait une template pour autoraiier un appel de fonction.
    Ca donnait d'abord ceci: auto raii_sdl = make_raii(&SDL_quit), puis carrément RAII<&SDL_quit> sdl_close;
    Malgré que les lignes précédentes expliquent bien le concept de RAII.
    Ça aide beaucoup quand ont a que 3 mois de C++ derrière soit a comprendre un des très nombreux concepts et toutes les nombreuses subtilités du C++...

    car par exemple en C pur il n'y a qu'un seule opérateur de cast:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    float nb = 3.14 ;
    int truncated_nb = (float) nb ;
    Alors que en C++ il y a plusieurs opérateurs de cast pour des cas différents...

    Et d'ailleurs tous le C++ est comme ça - loin de la simplicité du C (Une fois que l'on a vraiment compris) et donc je pense a une certaine privation de créativité parce que trop d'embûches sur la route de celle-ci (d'un code compiler sans erreur suite a une période de créativité, il faut avouer que la stderr de g++ est parfois vraiment verbeusement affreuse) et loin du code net clair et précis du C.

    Ai je bien fait de vendre mon âme de petit programmeur C au diable je pense que oui malgré tout.

    Je n'ai pas grand chose a dire a part cela.

    Seulement par exemple dans un jeu vidéo ou le vaisseau suit les mouvement de la souris que quel manière pensez écrire la gestion de l'affection de nouvelle coordonnées au vaisseaux ? Qui sera appelée constamment donc.

    PS: Il faut que je regarde mes références C pour y chercher le mot-clefs register (je crois que c'est en rapport avec les registres du processeur...????)...

    Sur ceux bon code C++ a vous.

  11. #11
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    Je vais essayer d'expliquer la template correspondante.
    Mais si je l'ai abandonnée, c'est pour son manque de souplesse. J'ai eu le choix entre une grosse usine ou simplement refaire rapidement une capsule à chaque besoin.

    Prenons la SDL, justement. Quoiqu'il arrive, il faut appeler SDL_Quit.
    La solution classique est d'appeler at_exit(&SDL_Quit);, pour forcer le programme à appeler la fonction en quittant.
    Sauf qu'il est possible de contourner at_exit.

    Du coup, la capsule RAII de base serait:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    struct RAII_SDL {
    ~RAII_SDL() {SDL_Quit();}
    };
    et le code proche de :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main() {
    //...plein de choses
    SDL_Init(...);
    RAII_SDL auto_close_sdl;
    //plein d'autres choses;
    return 0;
    }
    Tout le principe, c'est que auto_close_sdl est une variable. Son destructeur sera appelé quand on sortira de la fonction main().

    Passons à la template.
    Mon idée était d'avoir une classe template toute faite me permettant de ne plus écrire de capsule manuellement.
    En argument template, j'avais le type de la fonction a appeler, et tout devait fonctionner tout seul.

    Puis je suis tombé sur des fonctions de fermeture prenant des arguments. Et la classe est devenue trop complexe.

    Quand à la complexité relative des langages, c'est lié à ce que les langages n'ont pas les mêmes qualités.
    Une fois que tu connais les deux à fond, tu n'as pas vraiment un langage plus simple qu'un autre.

    Notamment, nous avons (je ne sais plus où sur le site) un article sur la qualité de controle d'erreur en C et C++, qui devrait t'intéresser.

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par Luke spywoker Voir le message
    PS: Il faut que je regarde mes références C pour y chercher le mot-clefs register (je crois que c'est en rapport avec les registres du processeur...????)...
    register permet d'indiquer que tu veux que la variable reste dans un registre et le compilo fera de son mieux pour garantir ça.
    Mais son utilisation de n'est pas vraiment conseillée, le compilo fera généralement un meilleur travail que toi pour déterminer quelles variables gardées dans des registres.

    Citation Envoyé par Luke spywoker Voir le message
    Et pour les quelques astuces présentés, je n'ai malheureusement que très peu compris le point suivant:
    Un temps, j'avais même fait une template pour autoraiier un appel de fonction.
    Ca donnait d'abord ceci: auto raii_sdl = make_raii(&SDL_quit), puis carrément RAII<&SDL_quit> sdl_close;
    Malgré que les lignes précédentes expliquent bien le concept de RAII.
    Le principe ne change pas : exécuter une fonction dans un destructeur pour garantir son exécution à un moment précis (-> quand la variable est détruite).
    Et si la variable est déclarée dans main elle sera détruite lors de la fermeture de l'application.

    Pour la syntaxe c'est juste du sucre syntaxique permis pas les templates / déduction de type. Le code peut ressembler à ça
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #include <iostream>
     
    template <class Fct>
    struct raii_capsule {
    	raii_capsule(Fct f) : m_fctPointer(f)
    	{ }
     
    	~raii_capsule() {
    		if (m_fctPointer) {
    			m_fctPointer();
    		}
    	}
     
    	raii_capsule(raii_capsule&& other) : m_fctPointer(other.m_fctPointer) {
    		other.m_fctPointer = nullptr;
    	}
     
    	raii_capsule(raii_capsule const&) = delete;
    	raii_capsule& operator=(raii_capsule const&) = delete;
    private:
    	Fct m_fctPointer;
    };
     
    template <class Fct>
    raii_capsule<Fct> make_raii(Fct f) {
    	return raii_capsule<Fct>(f);
    }
     
    void foo() {
    	std::cout << "foo" << std::endl;
    }
     
    int main(int, char**) {
    	{
    		auto c = make_raii(foo);
    	}
     
    	std::cout << "bar" << std::endl;
     
    	return 0;
    }
    Résultat :

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 630
    Points : 30 694
    Points
    30 694
    Par défaut
    Salut,
    Citation Envoyé par fred1599 Voir le message
    Pour l'héritage, franchement ça me gênerait, et je choisirais plutôt à utiliser des membres protégés (protected), inacessibles en dehors d'une classe, mais équivalents à des membres publics.
    A vrai dire, c'est une très mauvaise idée que de mettre des (variables) membres en <codeinline>protected</codeinline>, car cela pose le problème que c'est aux classes dérivées (filles / enfants) de s'assurer qu'elles respectent les invariants de la classe de base (mère).

    Cela pose moins de problèmes avec les fonctions -- virtuelles ou non -- car cela ne fait que permettre à "n'importe quelle fonction membre" de la classe dérivée de faire appel à la fonction définie comme protégée dans la classe de base, sans pour autant permettre de faire appel à cette même fonction depuis l'extérieur.

    Donc, en gros, il est possible d'avoir un code qui serait 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
     
    class Base{
    protected:
        void foo(/* paramètres*//){
          /* n'importe quoi qui modifie (ou non) les données membres qui
           * se trouvent dans l'accessibilité PRIVEE
       }
    private:
        /* quelques variables, dont on n'a pas besoin d'avoir le détail */
    };
    class Derivee{
    public:
        bar(/* paramètres */){
            foo(/*arguments */); // appelle Derivee::foo()
        }
    };
    et ce code serait très largement préférable à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Base{
    protected:
     
        /* quelques variables, dont on n'a pas besoin d'avoir le détail */
    };
    class Derivee{
    public:
        bar(/* paramètres */){
            /* modifie les variables protégées de Derivee, au risque de briser les invariants */
        }
    };
    En effet, le premier code t'apportera la garantie que seules les fonctions membre de Base iront modifier les données membres de Base, avec comme effet secondaire très désiré que, s'il y a un bug, nous aurons la certitude qu'il ne faut aller voir que du coté des fonctions membres de Base.
    Par contre, le deuxième code laisse la possibilité aux fonctions des classes dérivées d'aller modifier... les membres de Base, avec comme effet secondaire indésirable le fait que, s'il y a un bug au niveau de Base, nous devrons non seulement vérifier toutes les fonctions de Base, mais aussi toutes les fonctions de toutes les classes dérivées, ce qui risque de faire "un paquet"

  14. #14
    Membre expérimenté
    Avatar de Luke spywoker
    Homme Profil pro
    Etudiant informatique autodidacte
    Inscrit en
    Juin 2010
    Messages
    1 077
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Etudiant informatique autodidacte

    Informations forums :
    Inscription : Juin 2010
    Messages : 1 077
    Points : 1 742
    Points
    1 742
    Par défaut
    Merci pour vos réponses,

    Concernant l'explication de l'eternel, c'est parfaitement clair maintenant pour moi. Tout autant comme ton explication du principe du RAII qui m'a permis de mieux comprendre ce principe. Merci.

    Tout autant que le code exemple de Iradrille qui démontre une utilisation pratique de template de gestion de RAII, visant a automatiser le RAII.

    C'est enrichissant car cela va peut être me servir dans un futur proche car je me demandais comment forcer la destruction d'une frame: objet de type cv::Mat de la lib OpenCV.


    Concernant le mot-clef register c'est un peu confus pour moi ayant une mini expérience d'un mois d'apprentissage et de pratique de l'assembleur (ce qui s'est avéré très utile malgré que j'ai abandonner ce langage de programmation, simple en terme de logique mais trop de travail pour réellement écrire un programme concret.)
    Car si une variable occupe constamment (le temps qu'elle soit détruite) un registre du processeur ça va sérieusement handicaper le compilateur pour générer le code assembleur...
    Ce qui explique le conseil de ne pas utiliser ce mot-clef.
    Ou ce n'est peut-être pas le cas et peut être existe-t-il d'autres registres que ceux de l'assembleur, qui ne sont pas propre au processeur ?

    Avez vous remarquer que pour désigner regsiter et inline j'utilise le terme "mot-clef" alors que ce type de "mot-clef" font partie d'un ensemble qui porte un vrai nom et non mot-clef.
    Je viens de vérifier et corriger moi si je me trompe mais inline fait partie des: storage class specifier.
    Tout comme static, extern et auto (En C ou < std=c++11),
    qui influent sur le scope, la durée de vie donc et le linkage (je ne comprends pas pour ce dernier).
    Qui peuvent être de 2 types différents: statique et automatique selon mes sources.


    Concernant le spécificateur static je le connais bien:

    - Une variable déclaré comme static locale dans une fonction ne s'initialise que lors du premier appel de la fonction puis peut être modifier lors des d'autres appels (elle ne sera pas remise a la valeur d'initialisation du premier appel.).

    - Une variable déclaré comme static membre d'une classe doit être initialiser en dehors de la déclaration d'une classe (comme la définition une méthode). Et elle a pour particularité qu'il n'existe qu'une seule instance de celle-ci même si il existe plusieurs instance de la classe.

    - Une variable déclaré comme static globale est locale a un fichier de code source.

    Mais j'ai pour lacune, que je vous pris de bien vouloir combler en m'expliquant, pourquoi déclaré une fonction ou une méthode comme static, car je ne sais pas comment cela affecte la fonction ou méthode ???

    Car j'ai souvent vue des fonctions déclaré comme static mais je n'ai jamais compris pourquoi...



    Et j'ai une question pour koala01 car j'ai du mal a comprendre tes 2 exemple de 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
     
    class Base{
    protected:
        void foo(/* paramètres*//){
          /* n'importe quoi qui modifie (ou non) les données membres qui
           * se trouvent dans l'accessibilité PRIVEE
       }
    private:
        /* quelques variables, dont on n'a pas besoin d'avoir le détail */
    };
    class Derivee{
    public:
        bar(/* paramètres */){
            foo(/*arguments */); // appelle Derivee::foo()
        }
    };
    Tu n'a pas fait hériter la classe Derivee de la classe Base et tu appel foo() dans bar() mais de manière Derivee::foo() (externaliser) alors que tu parle de classe de base donc d'héritage ??? Est-ce volontaire ?

    Et dans ton deuxième code je ne vois pas le rapport entre les deux classes.

    Peut-être parle tu de classe de Base abstraite (ABC (Abstract Base Class) ne servant qu'a faire hérité), j'avoue que j'ai du mal a suivre ton code même si tes explications sont claires.

    Merci pour vos réponses éclairées illuminant les ténèbres de mon inexpérience en C++.

  15. #15
    Expert éminent sénior

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 031
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Software Developer
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 031
    Points : 11 474
    Points
    11 474
    Billets dans le blog
    11
    Par défaut
    [QWERTY]
    Pour register tu as le bon raisonnement, je pense, ca peut rendre la tache du compilateur plus complexe (mais nous, on s'en fiche hein!)
    Et quant a son utilisation, je ne l'ai utilise qu'une fois, lors de l'optimisation d'un code de conversion de dimensions de video (donc effectue a chaque frame affichee), et ca avait pas mal booste les perf, avant que je ne passe en SSE2 (c'est plus la meme chose )
    [/QWERTY]

  16. #16
    Membre éprouvé Avatar de fenkys
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    376
    Détails du profil
    Informations personnelles :
    Âge : 57
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Octobre 2007
    Messages : 376
    Points : 1 054
    Points
    1 054
    Par défaut
    Bonjour,


    Dans une classe, les membres statiques (que ce soit des fonctions ou des classes) existent indépendamment de toute instance de classe. On les appelle des membres de classe, par opposition aux membres non statiques appelés membre d'instance (ils nécessitent qu'une instance de classe soit crée pour être utilisée). Les membres statiques peuvent être utilisés directement sans instance. Un cas d'utilisation fréquent est dans le contrôle de la création d'une instance de classe, comme dans cette implémentation possible du pattern singleton :

    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
     
    class MaClasse 
    {
        private:
            MaClasse();
     
            static MaClasse * _instance;
        public:
           static MaClasse&  GetInstance();
           void foo();
    };
     
    MaClasse* MaClasse::_instance = nullptr;
     
    MaClasse& MaClasse::GetInstance()
    {
        if (_instance == nullptr) {
            _instance = new MaClasse();
        }
        return *_instance;
    }
    Et ça s'utilise comme çà :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    void MaFonction()
    {
        ...
        MaClasse::GetInstance().foo();
        ...
    }
    Ton objet est crée qu'une fois, il l'est dès que si tu en as besoin. Et quoi que tu fasses ailleurs dans ton code, tu n'auras qu'une seule instance de cette classe.

  17. #17
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 196
    Points : 17 165
    Points
    17 165
    Par défaut
    En C++, le mot clé static possède deux usages.

    Le premier est fortement déconseillé.
    Devant une entité déclarée au niveau du fichier (éventuellement dans un namespace), il spécifie que la déclaration est interne à l'unité de compilation.
    Cela permet de créer des variables globales internes à une bibliothèque, par exemple.
    C'est très aventageusement remplacé par un namespace anonyme (namespace {...}).

    L'autre usage est dans une déclaration de classe (ou de structure) ou dans le corps d'une fonction.

    Pour expliquer ce static simplement, j'utilise la métaphore du discours.
    Le code source est un moyen d'exprimer ce que tu souhaites que fasse un programme (écrit en binaire) généré par le compilateur.
    En fait, c'est une suite de demande faite à ce compilateur.

    Ainsi, quand j'écris int const x = 2;, ca revient à dire "soit x un entier constant, de valeur 2".
    Autre exemple: int f(); se traduit par "supposons une fonction f sans argument et retournant un entier".

    Je traduirai en général static en disant "qui n'est pas lié à la durée de vie d'une instance".

    Une fonction membre (ou méthode, c'est du vocabulaire) statique n'est pas liée à un objet de la classe. this n'y est pas défini.
    Une variable membre statique non plus, c'est une sorte de variable globale, dont le nom est composé du nom complet de la classe puis du sien propre.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    namespace exemple {
    class Bidule{
        static int chose;
    };
    }
    le nom exact de chose est: exemple::Bidule::chose.
    Une telle déclaration demande une définition unique (comme toute définition) dans une seule unité de compilation, généralement le .cpp définissant la classe.
    Elle s'écrira ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int exemple::Bidule::chose = 4;
    //ou
    namespace exemple {
    int Bidule::chose = 4;
    }
    static peut aussi apparaitre dans le corps d'une fonction.
    La variable "n'est pas lié à la durée de vie d'une instance" de l'appel de fonction.
    considérons cet exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int sequence() {
        static int i = 0;
        return ++i;
    }
    int main() {
        cout << sequence() << ' ' << sequence() << ' ' << sequence() << endl;
    }
    ce programme affichera: "1 2 3".
    En effet, au premier appel de sequence, le compilateur entend:
    appelle la fonction sequence, c'est à dire:
        soit l'entier qui n'est pas lié à la durée de vie d'une instance
        retourne la valeur de i pré-incrémentée.
    et au second:
    appelle la fonction sequence, c'est à dire:
        soit l'entier qui n'est pas lié à la durée de vie d'une instance
        retourne la valeur de i pré-incrémentée.
    Personnellement, je vois i comme la variable (privée) static int sequence()::i. Bien sûr, ca ne compilerait pas comme notation, mais c'est l'idée.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Liste des instructions de la bibl. std c++
    Par BBric dans le forum SL & STL
    Réponses: 7
    Dernier message: 29/10/2004, 01h02
  2. Sauvegarde std::vector dans un .ini
    Par mick74 dans le forum MFC
    Réponses: 2
    Dernier message: 12/05/2004, 14h30
  3. Recherche "étoilée" avec std::set
    Par guejo dans le forum MFC
    Réponses: 2
    Dernier message: 06/05/2004, 14h28
  4. std MFC
    Par philippe V dans le forum MFC
    Réponses: 7
    Dernier message: 17/01/2004, 01h54
  5. STL : std::set problème avec insert ...
    Par Big K. dans le forum MFC
    Réponses: 13
    Dernier message: 08/11/2003, 02h02

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