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

Langage C++ Discussion :

Pointeurs et classes : besoin d'éclaircissements


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut Pointeurs et classes : besoin d'éclaircissements
    Bonjour.

    Je me mets doucement à Qt, et j'aurai plusieurs questions relatives aux pointeurs et aux classes. En effet, on trouve plusieurs types de déclarations et je voudrais être certain de bien comprendre. Pour cela, je prendrais des exemples avec une "QString _chaine"


    1) Sans pointeurs

    Tout d'abord la méthode pour définir des variables membres classiques :
    Déclaration dans le header du type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    private :
    QString _chaine
    La _chaine est vraiment "membre" de la classe, elle est déclarée à la création de la classe et est détruite à la destruction de celle-ci. Dans n'importe quelles fonction membre de la classe je peux faire appel à _chaine et exécuter des fonctions du type


    2) Avec pointeurs

    Maintenant, je peux décider de passer par des pointeurs et c'est apparemment ce qu'il est courant d'utiliser avec Qt.
    Déclaration dans le header :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    private :
    QString *_chaine
    Ensuite, il faut obligatoirement passer par des allocation/désallocation de mémoire dans les fonctions membres de la classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    _chaine = new QString
    delete chaine
    Avec cette méthode j'ai plusieurs questions :
    2-1) L'allocation doit-elle se faire obligatoirement dans le constructeur de la classe et la désallocation dans le destructeur ? Ce que je veux dire par là c'est "la pratique d'une programmation "propre" veut-elle que lorsqu'une variable membre est déclarée en tant que pointeur on l'initialise dans le constructeur et on la delete dans le destructeur " ?
    2-2) Si on ne delete pas le pointeur dans le destructeur que se passe-t-il concrètement ?
    2-3) Si la réponse a la première question est "non" alors cela signifie que l'on peut très bien avoir des "_chaine = new QString" dans plusieurs fonctions membres de la classe : n'est-ce pas un peu la pagaille du coup au niveau de l'allocation/désallocation ?


    3) Avec pointeurs, autre version

    3ème syntaxe, que j'avoue avoir un peu du mal à comprendre à quoi elle sert et quelle sont les subtilités. Il s'agit de la syntaxe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    QString *_chaine = new QString ("Bonjour")
    3-1) Dans ce cas là il s'agit à la fois de la déclaration du pointeur et de l'allocation en mémoire c'est çà ?
    3-2) Si j'ai bien compris ce type de syntaxe permet de définir non pas des variables membres, mais des variables locales à certaines fonctions membres d'une classe. Et c'est là que cela devient plus difficile à comprendre pour moi : que se passe-t-il si l'on ne fait pas de "delete _chaine" à la fin de l'exécution de la fonction membre ? La variable locale est-elle automatiquement détruite à la fin de l'exécution de la fonction membre ?
    Merci de m'éclairer sur ce point.


    En tous les cas si vous avez des éclaircissements à m'apporter je suis preneur

  2. #2
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2007
    Messages : 11 517
    Par défaut
    Citation Envoyé par Kaluza Voir le message
    La _chaine est vraiment "membre" de la classe
    Oui
    Citation Envoyé par Kaluza Voir le message
    elle est déclarée à la création de la classe et est détruite à la destruction de celle-ci.
    Je dirais plutôt définie
    mais oui elle est définie à la création de la classe et détruite à la destruction de la classe.
    Citation Envoyé par Kaluza Voir le message
    Dans n'importe quelles fonction membre de la classe je peux faire appel à _chaine et exécuter des fonctions du type
    Oui à la condition que la fonction membre de la classe ne soit pas une fonction static.
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  3. #3
    Membre chevronné
    Avatar de haraelendil
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2004
    Messages
    283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2004
    Messages : 283
    Par défaut
    2-1)
    Tu n'est pas obligé d'instancié ta variable dans le constructeur, mais si tu ne le fait pas, tu devras tester si ton pointeur n'est pas nul à chaque fois que tu veux l'utiliser. Ca peut être utile de faire comme ça dans certain cas, mais dans la grande majorité, un instanciation dans le constructeur est bien plus simple.

    2-2) L'espace mémoire ou se trouve ta chaine ne sera jamais libéré, si tu as beaucoup d'instances de cette classe qui sont crées, ça finira pas ronger ta mémoire jusqu'à ce qu'y en ait pu de disponible, ça s'appelle une fuite mémoire.

    2-3) Oui, c'est la pagaille, il faut tester si ton pointeur est valide à chaque utilisation, et vérifier si il faut faire un delete avant chaque new


    3-1) Oui

    3-2) Oui la variable locale est détruite à la fin de la fonction membre, mais si celle-ci est un pointeur classique, le bout de mémoire sur lequel elle pointe ne le sera pas, fuite mémoire, comme en 2-2

  4. #4
    Membre expérimenté
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    188
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 188
    Par défaut
    Citation Envoyé par Kaluza Voir le message
    Maintenant, je peux décider de passer par des pointeurs et c'est apparemment ce qu'il est courant d'utiliser avec Qt.
    Attention il est en effet courant d'utiliser l'allocation dynamique dans Qt mais uniquement avec les classes qui héritent de QObject. En effet, à leur création, on peut leur donner un parent. Dans Qt c'est le parent qui s'occupe de la destruction des ses fils, tu n'as donc pas a t'en occuper.

    Ici tu utilise un QString qui n'hérite pas de QObject et qui ne peut donc utiliser cette mécanique. A priori pour une simple QString la solution 1 est plus adapté (mais bon tout dépend de ce que tu veux faire).

    voici un 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
     
    class Dialog : public QDialog
    {
        Q_OBJECT
     
    private:
        QPushButton* bouton1;
     
    public:
         Dialog(QWidget *parent = 0):
    	    QDialog(parent)
        {
    	bouton1 = new QPushButton("bouton1",this);
    	QPushButton* bouton2 = new QPushButton("bouton2",this);
        }
    };
    ici le deuxième argument du constructeur de QPushButton est le parent que l'on souhaite donné a notre bouton. Ainsi, même si j'ai créer dynamiquement mes boutons je n'ai pas a les détruire moi même avec un delete, ce sera fais automatiquement lorsque l'instance de ma classe Dialog sera elle même détruite.

  5. #5
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Bonjour,
    Le C++ propose 2 gestions de la mémoire : une gestion automatique et une gestion manuelle.
    La gestion automatique est celle utilisée lorsque tu déclares des variables par valeur (membre d'une classe, locale à une fonction, variable globale). Le C++ s'occupe tout seul d'obtenir la mémoire nécessaire et d'appeler le constructeur de la variable au moment opportun. De même, il appelle le destructeur et relâche la mémoire utilisée lorsqu'il le faut. Cette gestion est liée à la portée de la variable (celle de la fonction ou du bloc pour une variable locale, celle de l'objet la contenant pour un membre d'une classe).

    Une autre façon est de gérer manuellement, c'est à dire par le développeur, la durée de vie des variables. Cela se fait avec new et delete. new réserve la mémoire nécessaire et appelle le constructeur. delete appelle le destructeur puis libère la mémoire réservée. Les pointeurs permettent de récupérer l'adresse sur l'objet alloué et construit. L'avantage est que la durée de vie d'une variable peut être différente de celle qui contient cette variable. Ainsi, le membre d'une classe peut avoir une durée de vie plus courte que la variable qui la contient. Un autre avantage (qui n'est pas spécifique au C++ mais aux architectures PC) est que les allocations automatiques et les allocations dynamiques ne se font pas au même endroit : la pile pour celles gérées par le compilateur, le tas pour celles gérées manuellement. La pile est une zone en général plus petite que le tas et ne permet donc pas de stocker des variables trop importantes en taille. Les inconvénients de la gestion manuelle des pointeurs sont connus et cette gestion dynamique est souvent source de bug dont les classiques restent :
    => fuite mémoire : oubli d'un appel de delete sur une variable allouée dynamiquement ;
    => utilisation d'une variable libérée : on appelle delete mais on continue d'utiliser le pointeur sur la variable ;
    => double delete : comme précédemment, on appelle delete plusieurs fois sur la même zone mémoire ;
    etc.
    Pour cela, il existe une solution : les pointeurs intelligents ! Ce tutoriel présente la problématique et les pointeurs intelligents du langage. Qt possède ses propres pointeurs intelligents mais le concept reste le même.

    Ceci dit, pour répondre à tes questions :
    2-1) L'allocation doit-elle se faire obligatoirement dans le constructeur de la classe et la désallocation dans le destructeur ? Ce que je veux dire par là c'est "la pratique d'une programmation "propre" veut-elle que lorsqu'une variable membre est déclarée en tant que pointeur on l'initialise dans le constructeur et on la delete dans le destructeur " ?
    Non. Ce serait presque le contraire : si tu alloues systématiquement dans le constructeur et que tu libères systématiquement dans le destructeur, alors pourquoi t'occuper de la gestion de la mémoire manuellement ? Utilise une variable par valeur et laisse le compilateur s'en occuper automatiquement. Surtout pour des variables de type QString. La seule raison serait d'avoir une variable membre par pointeur qui suit strictement la durée de vie de l'objet la contenant serait une taille trop importante. Mais là il faut penser pointeurs intelligents
    2-2) Si on ne delete pas le pointeur dans le destructeur que se passe-t-il concrètement ?
    La zone mémoire n'est pas libérée. C'est ce qu'on appelle une fuite mémoire. Si tu fais ça de nombreuses fois, à un moment toute ta mémoire sera marquée comme réservée et utilisée et ton application ne pourra plus allouer de nouveaux objets dynamiquement.
    2-3) Si la réponse a la première question est "non" alors cela signifie que l'on peut très bien avoir des "_chaine = new QString" dans plusieurs fonctions membres de la classe : n'est-ce pas un peu la pagaille du coup au niveau de l'allocation/désallocation ?
    Oui on peut et oui c'est la pagaille. D'où les pointeurs intelligents
    3-1) Dans ce cas là il s'agit à la fois de la déclaration du pointeur et de l'allocation en mémoire c'est çà ?
    Beaucoup de choses se trament derrière cette innocente ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    QString *_chaine = new QString ("Bonjour")
    QString *_chaine définit un pointeur de type QString
    = initialise le pointeur à gauche avec la valeur de droite
    new QString ("Bonjour") : alloue la mémoire nécessaire à QString et appelle le constructeur à un argument (probablement QString::QString ( const char * str )) et retourne un pointeur de type QString sur cette nouvelle variable.
    3-2) Si j'ai bien compris ce type de syntaxe permet de définir non pas des variables membres, mais des variables locales à certaines fonctions membres d'une classe. Et c'est là que cela devient plus difficile à comprendre pour moi : que se passe-t-il si l'on ne fait pas de "delete _chaine" à la fin de l'exécution de la fonction membre ? La variable locale est-elle automatiquement détruite à la fin de l'exécution de la fonction membre ?
    Merci de m'éclairer sur ce point.
    Comme expliqué plus haut, si tu t'engages à gérer la mémoire manuellement avec new alors il est de ta responsabilité de la détruire avec delete. Si tu ne le fais pas, alors tu peux avoir une fuite mémoire. Le delete peut avoir lieu dans la fonction. Cependant si l'adresse est transmise à l'extérieur de la fonction, ce delete peut très bien avoir lieu ailleurs. Mais comme dit jusqu'à présent, ceci est une source récurrente de bugs. D'où les pointeurs intelligents

    C'est un autre débat, mais je m'interroge sur l'opportunité d'apprendre conjointement le C++ et Qt. Qt est un framework spécifique. Peut être ne faut-il pas mélanger les deux : d'abord acquérir de bonnes bases en C++ puis si cela t'intéresse aller vers Qt.

  6. #6
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut
    Merci à tous pour votre aide et vos explications, j'y vois déjà un peu plus clair.
    J'ai encore 3 questions :

    1) Dans le cadre de la programmation de classes Qt (donc héritées de QObject) y-a-t-il des règles "communément admises" de quand on doit utiliser des pointeurs ou des variables normales ? Parce qu'en regardant les exemples fournis avec le SDK, très très très souvent, on utilise des pointeurs.


    2) Questions beaucoup plus terre à terre maintenant :
    Mon but est d'afficher une QPixmap en passant par un QPainter, les deux étant variables membres d'une classe. La fonction de QPainter en question est :
    void drawPixmap ( int x, int y, const QPixmap & pixmap )
    Si je procède comme suit, cela ne fonctionne pas :
    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 MaClasse : public QWidget
    {
    Q_OBJECT
    public :
    MaClasse(const QString &fichierimage, QWidget *parent=0)
    private :
    QPainter *_painter
    QPixmap *_monimage
    };
     
    MaClasse::MaClasse(const QString &fichierimage, QWidget *parent):QWidget(parent)
    {
    _painter=new QPainter();
    _monimage=new QPixmap(fichierimage);
    _painter->drawPixmap(0,0,_monimage)
    }
    Erreur : no matching function for call to 'QPainter::drawPixmap(int,int,QPixmap*&)

    Si je procède comme cela, sans passer par des pointeurs pour le QPixmap, ça fonctionne bien :
    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 MaClasse : public QWidget
    {
    Q_OBJECT
    public :
    MaClasse(const QString &fichierimage, QWidget *parent=0)
    private :
    QPainter *_painter
    QPixmap _monimage
    };
     
    MaClasse::MaClasse(const QString &fichierimage, QWidget *parent):QWidget(parent)
    {
    _painter=new QPainter();
    _monimage.load(fichierimage);
    _painter->drawPixmap(0,0,_monimage)
    }
    Ma question est : comment faire fonctionner la première méthode en passant par des pointeurs pour à la fois QPainter et QPixmap.


    3)
    Enfin, j'ai un petit problème de "setter" en passant par des pointeurs.
    En effet, si je met
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    inline void setPath(const QString &path) 
    {
    _path=path;
    }
     
    ... avec un peu plus loin ....
    private :
    QString *_path
    ça ne fonctionne pas, alors que le code suivant fonctionne (sans pointeur):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    inline void setPath(const QString &path) 
    {
    _path=path;
    }
     
    ... avec un peu plus loin ....
    private :
    QString _path
    Je sais que c'est le "=" qui pose problème mais je ne vois pas comment faire un setter quand je passe par des pointeurs.

    Merci beaucoup

  7. #7
    Membre chevronné
    Avatar de haraelendil
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2004
    Messages
    283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2004
    Messages : 283
    Par défaut
    1)
    Qt fournissant un système de gestion de mémoire (et surtout de la destruction) de père en fils, il est intensivement ancré dans le framework que tous les objets héritant de QObject sont utilisés avec des pointeurs uniquement.
    Rien ne t'oblige à le faire, mais comme la majorité des argument suivent cette logique, si tu ne le fait pas, tu va passer ton temps à mettre des & et des * pour passer de valeur à adresse et vice versa.

    Par contre, pour les objets n'héritant pas de QObject (notamment ici QString et QPixmap), ils ont tendance à être utilisés par valeur, comme des variables classique (donc pareil, si tu veux pas galérer pour rien, tu devrais en faire de même)

    2)
    Déjà, des problèmes:
    si tu veux que ton painter dessine dans ton widget, il faut lui passé un pointeur sur ce widget (sinon il ne sait pas ou dessiner).
    Donc quand tu appelle le constructeur de ton QPainter, il faut lui passé this en paramètre.
    Cela dit, le painter est sensé effectuer ses actions de dessin dans une méthode prévue à cet effet ( void QWidget::paintEvent(QPaintEvent*); ), qu'il te faut redéfinir dans ta classe si tu veux dessiner dans ton widget, je ne sais pas trop ce que va donner le résultat si tu dessine dans le constructeur...

    Enfin, la réponse à ta question:
    drawPixmap prend en 3e paramètre un QPixmap, et non pas un pointeur sur un QPixmap, pour faire marche ta 1ere version, il faudrait faire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    _painter->drawPixmap(0, 0, *_pixmap);
    Enfin, c'est la même chose pour la 3.
    Si ta string est déclarée par valeur, l'opérateur = marche, par contre si le premier est un pointeur et le deuxième une instance de QString, ça ne marchera pas.

    PS: comme le dit Archi, tu a l'air d'être débutant en C++, aussi je ne saurai que trop te recommander de déjà commencer par apprendre le C++ "classique" avant de te mettre au framework Qt, qui fait appel à beaucoup de notions qui ont l'air de t'échapper.

  8. #8
    Membre expérimenté
    Profil pro
    Inscrit en
    Mars 2010
    Messages
    188
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations forums :
    Inscription : Mars 2010
    Messages : 188
    Par défaut
    Vu tes problèmes je ne peut qu'appuyer 3DArchi

    C'est un autre débat, mais je m'interroge sur l'opportunité d'apprendre conjointement le C++ et Qt. Qt est un framework spécifique. Peut être ne faut-il pas mélanger les deux : d'abord acquérir de bonnes bases en C++ puis si cela t'intéresse aller vers Qt.
    1)
    Dans le cadre de la programmation de classes Qt (donc héritées de QObject)
    Justement toute les classes de Qt n'héritent pas de QObject (QString par exemple mais aussi QPainter).
    En revanche pour toute les classes qui héritent de QObject il est vrai qu'elles sont souvent manipulées par pointeur pour les raisons que j'ai déjà expliqué mais aussi car beaucoup de fonction prenne des pointeur en paramètre dans Qt.

    2) et 3) :
    Tu sembles confondre pointeur et référence.

    Je te conseil vivement de bien comprendre ces notions (cf les tuto et la FAQ) avant de te mettre à Qt car le système parent/enfant risque de te faire prendre de mauvaises habitude si tu ne comprend pas ce que cela implique.

  9. #9
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut
    PS: comme le dit Archi, tu a l'air d'être débutant en C++, aussi je ne saurai que trop te recommander de déjà commencer par apprendre le C++ "classique" avant de te mettre au framework Qt, qui fait appel à beaucoup de notions qui ont l'air de t'échapper.
    En fait j'ai commencé le C++ il y a deux ans de mon côté (sans maîtriser préalablement le C), j'ai travaillé sur quelques projets de "computation", mais à part avec Qt, je n'ai jamais eu vraiment besoin de travailler avec des variables définies au travers de pointeurs à l'intérieur de classes. Le concept d'héritage est à peu près clair, c'est seulement qu'au niveau des pointeurs j'ai encore une conception assez floue des choses ... et Qt est pour moi la première occasion d'être confronté à ces problèmes. C'est pour cela que je bosse en même temps cet aspect du C++ et Qt.

    Je pense que je vais approfondir les exemples fournis avec le SDK de Qt, parce qu'en gros je sais "faire fonctionner" Qt pour parvenir à ce que j'ai en tête, mais je voudrais prendre de bonnes habitudes et éviter de coder ça n'importe comment (parce que pour l'instant c'est un peu le cas).

    Déjà, si j'ai bien compris, tout ce qui dérive de QObject est en général passé par pointeurs alors que ce n'est pas forcément le cas des autres composants de Qt.

Discussions similaires

  1. Pointeur structure : besoin d'éclaircissements
    Par julien.63 dans le forum C
    Réponses: 4
    Dernier message: 28/04/2007, 00h26
  2. Pointeurs de classes et méthodes
    Par insomniak dans le forum C++
    Réponses: 9
    Dernier message: 10/05/2006, 15h18
  3. polymorphisme, pointeurs et classes abstraites
    Par legend666 dans le forum C++
    Réponses: 10
    Dernier message: 02/11/2005, 16h44
  4. Réponses: 3
    Dernier message: 24/04/2005, 14h19
  5. [C#] Pointeur de classe
    Par papouAlain dans le forum Windows Forms
    Réponses: 6
    Dernier message: 06/01/2005, 12h32

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